diff --git a/.gitignore b/.gitignore index 9d678c0..f3c5e07 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ notes.txt res/stockfish/build/stockfish.exe res/stockfish/path.txt res/savedGames/game*.txt +res/log_game/* \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..485e17f --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,58 @@ +{ + "files.associations": { + "ratio": "cpp", + "iosfwd": "cpp", + "*.tcc": "cpp", + "array": "cpp", + "atomic": "cpp", + "bit": "cpp", + "cctype": "cpp", + "chrono": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "compare": "cpp", + "concepts": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "deque": "cpp", + "set": "cpp", + "string": "cpp", + "unordered_map": "cpp", + "vector": "cpp", + "exception": "cpp", + "algorithm": "cpp", + "functional": "cpp", + "iterator": "cpp", + "memory": "cpp", + "memory_resource": "cpp", + "numeric": "cpp", + "random": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "utility": "cpp", + "fstream": "cpp", + "initializer_list": "cpp", + "iostream": "cpp", + "istream": "cpp", + "limits": "cpp", + "new": "cpp", + "numbers": "cpp", + "ostream": "cpp", + "semaphore": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "stop_token": "cpp", + "streambuf": "cpp", + "thread": "cpp", + "typeinfo": "cpp" + } +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index baa3b73..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,105 +0,0 @@ -# Changelog -This is the basic changelog for My-PyChess. All dates are in dd/mm/yyyy format. - -## What's new in Version 3.2 (2/9/2020) -- 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. -- Added a game timer to multiplayer mode, with a new menu to setup timer. -- Fixed bugs and made many additions and improvements to the client-server in chess online gamemode. -- Made optimisations to core chess module and gui module. -- Added a chess hotwto. -- Minor improvements to game sounds and textbox. -- Upgraded preference menu and made the loadgame interface more robust. -- Several other minor changes and improvements made. - -## What was new in Version 3.1 (14/6/2020) -- Added basic sounds (beta). -- Added draw and resign option to online gameplay. -- Added a simple "About menu". -- Added compatibility for python v3.5 (server.py still needs python v3.6 to work) -- Made many changes to GUI throughout the app, including more prompt messages. -- Upgraded preferences. -- Fixed a small issue in move animations, added a new startgame animation. -- Fixed a bug where undo in singleplayer after game ended would give weird results. -- Fixed many bugs in online mode (server), made server robust to TCP packet loss among other things. - -## What was new in Version 3.0 (23/5/2020) -- THIS WAS THE BIGGEST RELEASE OF MY-PYCHESS EVER RELEASED. -- The code came with MIT License instead of the GPL-3 which came with v2.2. -- The code was revamped, fixing minor bugs and MAJOR PERFORMANCE IMPROVEMENTS. -- 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. -- Online play was added. This featured a ONLINE LOBBY and support upto 10 people to play chess(read more in ref/online.txt) -- Singleplayer saw BIG UPGRADES: A Good Menu, ability to play with a decent PYTHON CHESS ENGINE. -- Now, you can play against STOCKFISH CHESS ENGINE. My-PyChess will act as a GUI interface to give you this singleplayer mode. -- Included with this release, a good menu for stockfish and stockfish install/configure menu to help with installing and configuring stockfish with My-PyChess. -- Code was made more easy to understand with code comments throughout the code. -- EnPassant, Screen flip and Undo moves option were added. -- UI for loadGame menu was revamped, UI for preference menu was simplified. -- Save/load games with a new way of storing gamedata. - -## What was new in Version 2.3 (4/2/2020) -- This version never made it to github, I dropped it while working on it and started to focus on v3.0 -- This had included a few minor GUI changes. - -## What was new in Version 2.2 (22/1/2020) -- Minor performance upgrades. -- Further upgraded preferences. -- Upgraded loadgame menu (with delete games option). -- Minor changes made to the interface. -- While saving a game, it informed under what name the game was being saved. - -## What was new in Version 2.1 (11/1/2020) -- The upgrade optimised the game a bit, fixing a bug. -- When you clicked a piece, it would show all the available spaces it could go. -- The upgrade added a new preference menu where you could customise some game features. -- The upgrade added background animations on the home menu. - -## What was new in Version 2.0 (7/1/2020) -- Revamped the Home Menu so that it looks better. -- Revamped and code, optimised it. -- Fixed Bugs. -- Made some changes that ensure that it can work on all platforms. -- Added Save/load game features. -- Added highly basic SinglePlayer (random move generator) - -# Changelog for older versions - -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. - -## What was new in Version 1.5 (26/11/2019) -- added choice for pawn promotion -- added piece animations -- added some popup messages -- upgraded home menu -- fixed bugs - -## What was new in Version 1.4 (9/11/2019) -- Added a home menu -- Added placeholders for future upgrades -- Basic compliance with pep8 - -## What was new in Version 1.3 (2/11/2019) -- coding optimisations -- Checkmate and stalemate report feature added -- fixed bugs - -## What was new in Version 1.2 (24/10/2019) -- Improved move validation, stopping king from moving into attacked areas. -- fixed bugs - -## What was new in Version 1.1 (21/10/2019) -- added castling, pawn-promotion -- This version fixed a few bugs, but introduced lot more bugs - -## What was new in Version 1.0 (19/10/2019) -- Added Move validation for highly basic chess moves - -## What was new in Version 0.3 (17/10/2019) -- Added a few more basic utility functions. -- Fixed a few bugs. -- Minor changes made to code. - -## What was new in Version 0.2 (16/10/2019) -- Added GUI for pieces, implemented basic piece logic. - -## What was new in Version 0.1 (15/10/2019) -- Added GUI for the chess board diff --git a/LICENSE b/LICENSE deleted file mode 100644 index e1f02ae..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2020 Ankith(ankith26) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index 2eace2b..dee1806 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,6 @@ # Important Note -This project is not being maintained actively at the moment. I had started this -project back when I was still new to Python and pygame. While I enjoyed and -learnt a lot when I worked on this project, looking back at the codebase now -after over 2 years, I can't help but notice how bad the codebase is. -Please do not use this project as a base for any of your projects or for learning -python and/or pygame. There are many resources out there that are much better -than this project. - -If I ever return to this project, it would mean doing full re-write of the -entire codebase, with the current codebase being archived in a 'legacy' branch. + The project still has some unfinished features, which will be added later. ## My-PyChess @@ -25,12 +16,11 @@ Any bug-reports, suggestions or questions, you can leave it in the github issues 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). ### Getting started - +- `pip3 install -r requirement.txt` - Make sure you have Python and `pygame` installed and working. - Clone this repository (or download zip file and extract it). - Then, run the `pychess.py` file. Trying to run any other file will not run the game. - -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. +- Run server if play online: `g++ server.cpp -o server && ./server` ### Features @@ -40,30 +30,13 @@ There is also a [lite implementation](https://github.com/ankith26/My-PyChess-lit - Supports things like castling, pawn promotion, enpassent etc. - Supports saving and loading games. - Has single player mode with two different types, levels and ability to play against the stockfish chess engine. -- Has online gamemode, play chess with anyone in the world. +- Has online gamemode, play chess with anyone in the world using login/ logout. - Has a chess game timer. - Has a preference menu where you can customize the app to meet your preferences. - Has a chess howto, about menu and stockfish install/configure menu to make things easy for users. +- Use the Elo scoring mechanism according to world standards. +- There is a feature to find opponents based on elo point difference -## What's new in this Version 3.2 -- 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. -- Added a game timer to multiplayer mode, with a new menu to setup the timer. -- Fixed bugs and made many additions and improvements to the client-server in chess online gamemode. -- Made optimisations to core chess module and gui module. -- Added a chess hotwto. -- Minor improvements to game sounds and textbox. -- Upgraded preference menu and made the loadgame interface more robust. -- Several other minor changes and improvements made. - -## Highlights of v3.x, the latest major release. -- The code was revamped and restructured, fixed bugs and made **major performance improvements**. -- **Online play** was added. This features a online lobby and support upto 10 people to play chess. -- Singleplayer saw big upgrades: Firstly, a decent **python chess engine** that playes chess was added. -- You can play against **stockfish chess engine** (see https://stockfishchess.org). My-PyChess will act as an interface to give you this singleplayer mode. -- En-Passant in chess, undo move, screenflip and sounds were added. -- Lots of changes made to GUI. - -Click [here](CHANGELOG.md) to see full changelog. ### Online Gameplay diff --git a/VERSION b/VERSION deleted file mode 100644 index 6d260c3..0000000 --- a/VERSION +++ /dev/null @@ -1 +0,0 @@ -v3.2.0 diff --git a/account.txt b/account.txt new file mode 100644 index 0000000..7e61f87 --- /dev/null +++ b/account.txt @@ -0,0 +1,4 @@ +hoan 1234 1 +ky 1234 1 +h 1 1 +a 1 1 \ No newline at end of file diff --git a/chess/lib/__init__.py b/chess/lib/__init__.py index 1b33ebb..68cabde 100644 --- a/chess/lib/__init__.py +++ b/chess/lib/__init__.py @@ -179,3 +179,41 @@ def showScreen(win, side, board, flags, pos, load, player=None, online=False): if not multi: pygame.display.update() +def showScreen_view(win, side, board, flags, pos, player=None, online=False): + multi = False + if player is None: + multi = True + player = side + flip = False + + drawBoard(win) + win.blit(BACK, (460, 0)) + + if not multi: + win.blit(CHESS.TURN[int(side == player)], (10, 460)) + + if not online: + win.blit(CHESS.SAVE, (350, 462)) + + if isEnd(side, board, flags): + if isChecked(side, board): + win.blit(CHESS.CHECKMATE, (100, 12)) + win.blit(CHESS.LOST, (320, 12)) + win.blit(CHESS.PIECES[side]["k"], (270, 0)) + else: + win.blit(CHESS.STALEMATE, (160, 12)) + else: + if online: + win.blit(CHESS.DRAW, (10, 12)) + win.blit(CHESS.RESIGN, (400, 462)) + + if isChecked(side, board): + win.blit(CHESS.CHECK, (200, 12)) + if isOccupied(side, board, pos) and side == player: + x = (9 - pos[0]) * 50 if flip else pos[0] * 50 + y = (9 - pos[1]) * 50 if flip else pos[1] * 50 + pygame.draw.rect(win, (255, 255, 0), (x, y, 50, 50)) + drawPieces(win, board, flip) + + if not multi: + pygame.display.update() \ No newline at end of file diff --git a/chess/lib/core.py b/chess/lib/core.py index 29ea356..22c60e6 100644 --- a/chess/lib/core.py +++ b/chess/lib/core.py @@ -42,9 +42,11 @@ def legalMoves(side, board, flags): # This function returns wether a game has ended or not def isEnd(side, board, flags): - for _ in legalMoves(side, board, flags): - return False - return True + # Check if the king of the given side is present in the board + king_present = any(piece[2] == "k" for piece in board[side]) + + # If the king is not present, the game has ended + return not king_present # This function moves the piece from one coordinate to other while handling the # capture of enemy, pawn promotion and en-passent. @@ -80,7 +82,8 @@ def move(side, board, fro, to, promote="p"): # This function returns wether a move puts ones own king at check def moveTest(side, board, fro, to): - return not isChecked(side, move(side, copy(board), fro, to)) + # return not isChecked(side, move(side, copy(board), fro, to)) + return True # This function returns wether a move is valid or not def isValidMove(side, board, flags, fro, to): @@ -94,6 +97,9 @@ def isValidMove(side, board, flags, fro, to): def makeMove(side, board, fro, to, flags, promote="q"): newboard = move(side, copy(board), fro, to, promote) newflags = updateFlags(side, newboard, fro, to, flags) + if isEnd(side, newboard, newflags): + print("Game Over! The king has been captured.") + # You might want to handle the end of the game here return not side, newboard, newflags # Does a routine check to update all the flags required for castling and diff --git a/chess/lib/gui.py b/chess/lib/gui.py index 94f2559..1bae033 100644 --- a/chess/lib/gui.py +++ b/chess/lib/gui.py @@ -134,7 +134,7 @@ def prompt(win, msg=None): # optimising images for display - call only once per game def start(win, load): convertPieces(win) - sound.play_start(load) + # sound.play_start(load) clk = pygame.time.Clock() for i in range(101): clk.tick_busy_loop(140) diff --git a/chess/online.py b/chess/online.py index 0617a9d..9a36f41 100644 --- a/chess/online.py +++ b/chess/online.py @@ -7,24 +7,21 @@ ''' import socket import threading +from time import sleep from chess.onlinelib import * +import menus VERSION = "v3.2.0" PORT = 26104 # This is a main function that calls all other functions, socket initialisation # and the screen that appears just after online menu but just before online lobby. -def main(win, addr, load, ipv6=False): - showLoading(win) - - if ipv6: - sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) - servaddr = (addr, PORT, 0, 0) - else: - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - servaddr = (addr, PORT) +def main(win, username, password, load, ipv6=False): + # Tai day gui request dang nhap + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + servaddr = ('127.0.0.1', PORT) try: sock.connect(servaddr) @@ -34,6 +31,17 @@ def main(win, addr, load, ipv6=False): thread = threading.Thread(target=bgThread, args=(sock,)) thread.start() + + write(sock, username) + write(sock, password) + # Đợi server phản hồi "OK" để xác nhận username và password hợp lệ + while read() != "OK": + ret = menus.onlinemenu(win) + username, password = ret[0], ret[1] + write(sock, username) + write(sock, password) + + #TODO: check username, password write(sock, "PyChess") write(sock, VERSION) @@ -49,12 +57,10 @@ def main(win, addr, load, ipv6=False): showLoading(win, 4) elif msg.startswith("key"): - ret = lobby(win, sock, int(msg[3:]), load) - + ret = lobby(win, sock, int(msg[3:]), load) else: print(msg) showLoading(win, 5) - write(sock, "quit") sock.close() thread.join() diff --git a/chess/onlinelib/__init__.py b/chess/onlinelib/__init__.py index 63e3a5c..decd8d4 100644 --- a/chess/onlinelib/__init__.py +++ b/chess/onlinelib/__init__.py @@ -4,11 +4,14 @@ other functions for importing from online.py ''' from chess.lib import * +from chess.onlinelib.sockutils import getHistory from chess.onlinelib.utils import ( bgThread, read, readable, flush, + showHistory, + waiting, write, getPlayers, showUpdateList, @@ -16,6 +19,8 @@ popup, request, draw, + draw_win, + rematch, showLobby, ) @@ -26,11 +31,9 @@ def lobby(win, sock, key, load): playerList = getPlayers(sock) while True: clock.tick(10) - if playerList is None: - return 2 + return 2 showLobby(win, key, playerList) - for event in pygame.event.get(): if event.type == pygame.QUIT: write(sock, "quit") @@ -44,7 +47,27 @@ def lobby(win, sock, key, load): if 270 < x < 300 and 85 < y < 115: playerList = getPlayers(sock) - + + if 330 < x < 400 and 85 < y < 115: + history = getHistory(sock) + break_while = False + + while True: + showHistory(win, key, history) + for event in pygame.event.get(): + if event.type == pygame.QUIT: + write(sock, "quit") + return 0 + if event.type == pygame.MOUSEBUTTONDOWN: + x, y = event.pos + if 460 < x < 500 and 0 < y < 50: + break_while = True + break # This breaks out of the for loop + + if break_while: + break # This breaks out of the while loop + + if 300 < x < 475: for i in range(len(playerList)): if 122 + 30 * i < y < 148 + 30 * i: @@ -58,7 +81,6 @@ def lobby(win, sock, key, load): ret = request(win, sock) if ret in [0, 1, 2]: return ret - elif ret == 4: newret = chess(win, sock, 0, load) if newret in [0, 1, 2]: @@ -69,14 +91,26 @@ def lobby(win, sock, key, load): playerList = getPlayers(sock) break + if 200 < x < 300 and 450 < y < 480: + write(sock,"find") + msg = read() + print(msg) + if msg == "msgOk": + ret = waiting(win, sock) + newret = chess(win, sock, 0, load) + if newret in [0, 1, 2]: + return newret + if readable(): msg = read() + # print(f"{msg}") # Tai sao lai chay toi day if msg == "close": return 2 - elif msg.startswith("gr"): ret = request(win, sock, msg[2:]) + # write(sock, "ready") + ret = 4 if ret == 4: write(sock, "gmOk" + msg[2:]) newret = chess(win, sock, 1, load) @@ -88,12 +122,41 @@ def lobby(win, sock, key, load): if ret == 2: return ret playerList = getPlayers(sock) + elif msg.startswith("xr"): + write(sock, "ready") + write(sock, "gmOk" + msg[2:]) + newret = chess(win, sock, 1, load) + if newret in [0, 1, 2]: + return newret + playerList = getPlayers(sock) + +def save_game_to_pgn(moves, filename="game.pgn"): + with open(filename, "w") as pgn_file: + pgn_file.write("[Event \"My Chess Game\"]\n") + pgn_file.write("[Site \"Chess Board\"]\n") + pgn_file.write("[Date \"2023.12.21\"]\n") + pgn_file.write("[Round \"-\"]\n") + pgn_file.write("[White \"Player 1\"]\n") + pgn_file.write("[Black \"Player 2\"]\n") + pgn_file.write("[Result \"*\"]\n\n") + + for i, move in enumerate(moves, start=1): + if i % 2 == 1: + pgn_file.write(f"{i // 2 + 1}. {move} ") + else: + pgn_file.write(move + " ") + + if len(moves) % 2 == 1: + pgn_file.write("*") + else: + pgn_file.write("\n*\n") # This is called when user enters chess match, handles online chess. -def chess(win, sock, player, load): +def chess(win, sock, player, load, pgn_filename="game.pgn"): start(win, load) side, board, flags = initBoardVars() + moves = [] # List to store moves for PGN clock = pygame.time.Clock() sel = prevsel = [0, 0] @@ -109,7 +172,7 @@ def chess(win, sock, player, load): if 460 < x < 500 and 0 < y < 50: write(sock, "end") return 3 - + if 50 < x < 450 and 50 < y < 450: x, y = x // 50, y // 50 if load["flip"] and player: @@ -122,14 +185,26 @@ def chess(win, sock, player, load): sel = [x, y] if (side == player - and isValidMove(side, board, flags, prevsel, sel)): + and isValidMove(side, board, flags, prevsel, sel)): promote = getPromote(win, player, board, prevsel, sel) write(sock, "mov" + encode(prevsel, sel, promote)) - + animate(win, player, board, prevsel, sel, load, player) side, board, flags = makeMove( side, board, prevsel, sel, flags, promote) + # Save move to PGN + pgn_move = encode(prevsel, sel, promote) + moves.append(pgn_move) + + # Save PGN after each move + save_game_to_pgn(moves, pgn_filename) + + if isEnd(side, board, flags): + write(sock, "win") + # ret = rematch(win, sock) + ret = draw_win(win, sock) + return ret elif not isEnd(side, board, flags): if 0 < x < 70 and 0 < y < 50: write(sock, "draw?") @@ -146,10 +221,9 @@ def chess(win, sock, player, load): msg = read() if msg == "close": return 2 - elif msg == "quit" or msg == "resign": return popup(win, sock, msg) - + elif msg == "end": msg = "end" if isEnd(side, board, flags) else "abandon" return popup(win, sock, msg) @@ -157,8 +231,11 @@ def chess(win, sock, player, load): elif msg == "draw?": ret = draw(win, sock, False) if ret in [2, 3]: - return ret - + return + elif msg == "win": + ret = draw_win(win, sock, False) + # ret = rematch(win, sock) + return ret elif msg.startswith("mov") and side != player: fro, to, promote = decode(msg[3:]) if isValidMove(side, board, flags, fro, to): diff --git a/chess/onlinelib/sockutils.py b/chess/onlinelib/sockutils.py index d605757..64c3f74 100644 --- a/chess/onlinelib/sockutils.py +++ b/chess/onlinelib/sockutils.py @@ -16,8 +16,7 @@ def bgThread(sock): isdead = False while True: try: - msg = sock.recv(8).decode("utf-8").strip() - + msg = sock.recv(1024).decode("utf-8").strip() except: break @@ -64,6 +63,21 @@ def write(sock, msg): # A function to query the server for number of people online, returns a list # of players connected to server if all went well, None otherwise. +def getHistory(sock): + if not flush(): + return None + write(sock, "his") + msg = read() + if msg.startswith("xnum"): + data = [] + for i in range(int(msg[4:6])): + newmsg = read() + if newmsg == "close": + return None + else: + data.append(newmsg) + return tuple(data) + def getPlayers(sock): if not flush(): return None diff --git a/chess/onlinelib/utils.py b/chess/onlinelib/utils.py index b10eb1e..de763ae 100644 --- a/chess/onlinelib/utils.py +++ b/chess/onlinelib/utils.py @@ -3,6 +3,7 @@ In this file, we define the gui functions for online chess. ''' +import datetime import pygame from chess.onlinelib.sockutils import * @@ -71,7 +72,7 @@ def request(win, sock, key=None): pygame.draw.rect(win, (0, 0, 0), (100, 210, 300, 100)) pygame.draw.rect(win, (255, 255, 255), (100, 210, 300, 100), 4) - win.blit(ONLINE.REQUEST1[0], (120, 220)) + win.blit(ONLINE.WAITING1[0], (120, 220)) win.blit(ONLINE.REQUEST1[1], (105, 245)) win.blit(ONLINE.REQUEST1[2], (135, 270)) @@ -128,8 +129,28 @@ def request(win, sock, key=None): # It shows a popup message on the screen. It can show two things, # 1) Waiting for other players input for draw game (when requester is True) # 2) Waiting for players input for draw game (when requester is False) +def waiting(win, sock): + pygame.draw.rect(win, (0, 0, 0), (100, 210, 300, 100)) + pygame.draw.rect(win, (255, 255, 255), (100, 210, 300, 100), 4) + + win.blit(ONLINE.WAITING1[0], (120, 220)) + while True: + if readable(): + msg = read() + if msg == "nostart": + write(sock, "pass") + return 3 + + if msg == "start": + write(sock, "ready") + return 4 + if msg.startswith("xr"): + write(sock, "ready") + return 4 + + def draw(win, sock, requester=True): - if requester: + if not requester: pygame.draw.rect(win, (0, 0, 0), (100, 220, 300, 60)) pygame.draw.rect(win, (255, 255, 255), (100, 220, 300, 60), 4) @@ -170,17 +191,97 @@ def draw(win, sock, requester=True): msg = read() if msg == "close": return 2 - + if msg == "win": + return popup(win, sock, msg) if msg == "quit": return popup(win, sock, msg) if requester: if msg == "draw": return popup(win, sock, msg) - if msg == "nodraw": return 4 - + +def rematch(win, sock, requester=True): + pygame.draw.rect(win, (0, 0, 0), (100, 160, 300, 130)) + pygame.draw.rect(win, (255, 255, 255), (100, 160, 300, 130), 4) + + win.blit(ONLINE.DRAW2[0], (120, 170)) + win.blit(ONLINE.DRAW2[1], (170, 195)) + + win.blit(ONLINE.OK, (145, 240)) + win.blit(ONLINE.NO, (305, 240)) + pygame.draw.rect(win, (255, 255, 255), (140, 240, 50, 28), 2) + pygame.draw.rect(win, (255, 255, 255), (300, 240, 50, 28), 2) + + pygame.display.flip() + + while True: + for event in pygame.event.get(): + if event.type == pygame.MOUSEBUTTONDOWN: + if 240 < event.pos[1] < 270: + if 140 < event.pos[0] < 190: + write(sock, "acc") + return 3 + elif 300 < event.pos[0] < 350: + write(sock, "dec") + return 4 + if readable(): + msg = read() + print(f'{msg}__') +def draw_win(win, sock, requester=True): + if requester: + pygame.draw.rect(win, (0, 0, 0), (100, 160, 300, 130)) + pygame.draw.rect(win, (255, 255, 255), (100, 160, 300, 130), 4) + + win.blit(ONLINE.WIN2[0], (225, 195)) + + win.blit(ONLINE.OK, (240, 240)) + pygame.draw.rect(win, (255, 255, 255), (235, 240, 50, 28), 2) + + else: + pygame.draw.rect(win, (0, 0, 0), (100, 160, 300, 130)) + pygame.draw.rect(win, (255, 255, 255), (100, 160, 300, 130), 4) + + win.blit(ONLINE.WIN1[0], (225, 195)) + + win.blit(ONLINE.OK, (240, 240)) + pygame.draw.rect(win, (255, 255, 255), (235, 240, 50, 28), 2) + + pygame.display.flip() + while True: + for event in pygame.event.get(): + if requester: + if event.type == pygame.QUIT: + write(sock, "quit") + return 0 + if event.type == pygame.MOUSEBUTTONDOWN: + if 240 < event.pos[1] < 270: + if 240 < event.pos[0] < 290: + write(sock, "win") + return 3 + elif event.type == pygame.MOUSEBUTTONDOWN: + if 240 < event.pos[1] < 270: + if 240 < event.pos[0] < 290: + write(sock, "lose") + return 3 + elif 300 < event.pos[0] < 350: + write(sock, "nodraw") + return 4 + if readable(): + msg = read() + if msg == "close": + return 2 + if msg == "win": + return popup(win, sock, msg) + if msg == "quit": + return popup(win, sock, msg) + + if requester: + if msg == "draw": + return popup(win, sock, msg) + if msg == "nodraw": + return 4 # Responsible for showing the online Lobby def showLobby(win, key, playerlist): win.fill((0, 0, 0)) @@ -190,14 +291,16 @@ def showLobby(win, key, playerlist): win.blit(BACK, (460, 0)) win.blit(ONLINE.LIST, (20, 75)) win.blit(ONLINE.REFRESH, (270, 85)) + win.blit(ONLINE.HISTORY, (330, 85)) pygame.draw.line(win, (255, 255, 255), (20, 114), (190, 114), 3) pygame.draw.line(win, (255, 255, 255), (210, 114), (265, 114), 3) if not playerlist: win.blit(ONLINE.EMPTY, (25, 130)) + # TODO: Hien thi so diem for cnt, player in enumerate(playerlist): - pkey, stat = int(player[:4]), player[4] + pkey, stat, elo = int(player[:4]), player[4], player[5:] yCord = 120 + cnt * 30 putLargeNum(win, cnt + 1, (20, yCord)) @@ -208,12 +311,41 @@ def showLobby(win, key, playerlist): win.blit(ONLINE.ACTIVE, (200, yCord)) elif stat == "b": win.blit(ONLINE.BUSY, (200, yCord)) - pygame.draw.rect(win, (255, 255, 255), (300, yCord + 2, 175, 26), 2) - win.blit(ONLINE.REQ, (300, yCord)) + pygame.draw.rect(win, (255, 255, 255), (350, yCord + 2, 120, 26), 2) + putLargeNum(win, elo, (300, yCord)) + win.blit(ONLINE.REQ, (360, yCord)) - win.blit(ONLINE.YOUARE, (100, 430)) - pygame.draw.rect(win, (255, 255, 255), (250, 435, 158, 40), 3) - win.blit(ONLINE.PLAYER, (260, 440)) - putLargeNum(win, key, (340, 440)) - - pygame.display.update() \ No newline at end of file + win.blit(ONLINE.FIND_MATCH, (200, 450)) + # win.blit(ONLINE.YOUARE, (100, 430)) + # pygame.draw.rect(win, (255, 255, 255), (250, 435, 158, 40), 3) + # win.blit(ONLINE.PLAYER, (260, 440)) + # putLargeNum(win, key, (340, 440)) + pygame.display.update() + +def showHistory(win, key, history): + win.fill((0, 0, 0)) + + win.blit(ONLINE.HISTORY_TITLED, (140, 14)) + pygame.draw.rect(win, (255, 255, 255), (65, 10, 355, 68), 4) + win.blit(BACK, (460, 0)) + if not history: + win.blit(ONLINE.EMPTY_HISTORY, (25, 130)) + for cnt, his in enumerate(history): + parts = his.split() + username = parts[0] + time = parts[1] + status = parts[2] + yCord = 120 + cnt * 30 + + # win.blit(ONLINE.DOT, (20, yCord)) + # win.blit(ONLINE.PLAYER, (52, yCord)) + putLargeNum(win, username, (20, yCord)) + if status == '1': + win.blit(ONLINE.WIN_STATUS, (110, yCord)) + elif status == "0": + win.blit(ONLINE.LOSE_STATUS, (110, yCord)) + year, month, day, hour, minute = map(int, time.split('-')) + output_str = '{:02d}-{:02d}-{} {:02d}:{:02d}'.format(day, month, year, hour, minute) + putLargeNum(win, output_str, (230, yCord)) + pygame.display.update() + \ No newline at end of file diff --git a/chess/singleplayer.py b/chess/singleplayer.py index 42f2c31..c393c63 100644 --- a/chess/singleplayer.py +++ b/chess/singleplayer.py @@ -12,6 +12,14 @@ from chess.lib import * from ext.pyFish import StockFish + +def helper_function(): + pass + +def nl(): + print("") + + # Run main code for chess singleplayer (stockfish) def main(win, player, level, load, movestr=""): fish = StockFish(getSFpath(), level) diff --git a/menus/online.py b/menus/online.py index f4c39ec..a558b98 100644 --- a/menus/online.py +++ b/menus/online.py @@ -6,7 +6,7 @@ import pygame from ext.pyBox import TextBox -from tools.loader import ONLINEMENU, BACK, FONT +from tools.loader import ONLINEMENU, BACK, FONT, LOGINBOARD from tools.utils import rounded_rect # This shows the screen @@ -23,25 +23,43 @@ def showScreen(win, sel): rounded_rect(win, (255, 255, 255), (300, 350, 110, 30), 10, 3) win.blit(ONLINEMENU.CONNECT, (300, 350)) - + pygame.draw.rect(win, (255, 255, 255), (130 + sel*160, 460, 40, 20), 3) +def loginScreen(win, sel): + win.fill((0, 0, 0)) + rounded_rect(win, (255, 255, 255), (120, 10, 260, 70), 20, 4) + rounded_rect(win, (255, 255, 255), (20, 90, 460, 400), 14, 4) + win.blit(LOGINBOARD.HEAD, (175, 15)) + win.blit(BACK, (460, 0)) + + for cnt, i in enumerate(LOGINBOARD.TEXT): + win.blit(i, (40, 100 + cnt*18)) + + rounded_rect(win, (255, 255, 255), (300, 350, 110, 30), 10, 3) + win.blit(LOGINBOARD.CONNECT, (300, 350)) # This is the main function, called from main menu def main(win): clock = pygame.time.Clock() sel = 0 - box = TextBox(FONT, (0, 0, 0), (65, 350, 200, 35)) + username_box = TextBox(FONT, (0, 0, 0), (65, 350, 200, 35)) + password_box = TextBox(FONT, (0, 0, 0), (65, 400, 200, 35)) # Adjusted position for password box + while True: clock.tick(24) - showScreen(win, sel) + loginScreen(win, sel) pygame.draw.rect(win, (255, 255, 255), (63, 348, 204, 39)) - box.draw(win) + username_box.draw(win) + + pygame.draw.rect(win, (255, 255, 255), (63, 398, 204, 39)) # Draw rectangle for password box + password_box.draw(win) for event in pygame.event.get(): - box.push(event) + username_box.push(event) + password_box.push(event) if event.type == pygame.QUIT: return 0 @@ -59,5 +77,5 @@ def main(win): sel = 1 if 300 < x < 410 and 350 < y < 380: - return box.text, bool(sel) + return username_box.text, password_box.text, bool(sel) # Return both username and password pygame.display.update() diff --git a/onelinehowto.txt b/onelinehowto.txt new file mode 100644 index 0000000..ccfc856 --- /dev/null +++ b/onelinehowto.txt @@ -0,0 +1,13 @@ +* Account: account.txt +Descriptions: + The file includes 3 columns: Username, Password, Status. + Username, Password. + Status: Between 0 & 1. + 0: Block - Cant Login + 1: Avaiable +* username.txt +Descriptions: + The file includes 3 columns: Username, ID, Elo Score. + ID: + Elo Score + diff --git a/onlinehowto.txt b/onlinehowto.txt deleted file mode 100644 index 251f882..0000000 --- a/onlinehowto.txt +++ /dev/null @@ -1,107 +0,0 @@ -=============================================================== - Online Gameplay - --------------- -=============================================================== -Online Gameplay consists of two elements, client and server. - -CLIENT: It is the machine that runs My-PyChess code. -SERVER: It is a machine that accepts connections from different clients(upto 10) - -Back when I was actively working on this project, the networking element of this -app was made with security in mind. I had not used the python "pickle" module, -which is normally used in such applications. This has security risks associated. - -The server keeps absolutely NO DATA of any user that connects to it. -No names, accounts, emails, IP address, Location - nothing. PRIVACY IS RESPECTED. - -The client and server speak over Raw TCP protocol with no sort of encryption. -This means that any bad guys may read the messages that the client and server -send to one another. But the best part is the fact that the client and server DO NOT -EXCHANGE ANY SENSITIVE DATA. This means that the information is practically useless -for the bad guys. So they will not invest their time and efforts to do such things. -If they did so, they would be in major dissapointment. - -The public global test server is not being hosted anymore. - -========================================================================== - SELF-HOSTING MY_PYCHESS SERVER -========================================================================== - -HOW TO RUN SERVER: -================== -Run server.py, as simple as that! -Python v3.6 or above has to be used. - -HOW TO CONNECT TO THIS SERVER: -============================== -The server and client must be connected to the same router. When the server runs, -it will message it's IP address in the shell. Enter this in the client's menu. -IT IS TO BE NOTED THAT THE IP ADDRESS IT GIVES IS THE PRIVATE IP ADDRESS, NOT PUBLIC - -If for some reason, you want the server's PUBLIC IP ADDRESS, -I have covered that part below. This will not be required for most usecases. - -HOW TO CONFIGURE THE SERVER (FOR ADVANCED USAGE): -================================================= - -On top of server.py, there are two constants - LOG and IPV6. -They are set to False by default. -IT IS RECOMMENDED YOU DO NOT CHANGE THESE. -But if you really need to, then you can change those. - -If you set 'LOG = True', the server will log it's output to a file. The file -will be named by the current date and time of logging. This feature is not -recommended because it will consume more CPU power and RAM than when it is not -logging. - -If you set 'IPV6 = True', the server will start in IPv6 mode. This means that -any client that is using the IPv6 protocol can connect to the server. Both client -and server must support IPv6 for this to work. By default, the server opens in -IPv4 mode, which is the protocol that is supported by most of the systems of today. - -IPv6 is a more modern protocol, that will take time to be widely adopted because -most of the internet runs on IPv4. - -MANAGING THE SERVER WHILE IT IS RUNNING -======================================= - -When the server is running, it is constantly messaging you whatever is going -on. You can enter commands to the server. - -Commands are listed below: -1) report - Gives the current status of the server with useful information like the - number of threads the server is running, number of people online etc etc - -2) mypublicip - Enter this command to know the server's public IP Address. - -3) lock - This will put the server in a "locked" state. After this, anyone will not - be able to connect to the server. This will not affect anyone who is - aldready connected to the server. - -4) unlock - This will "unlock" the server, the server that is locked will be "unlocked", - then anybody can connect to the server. This is the default state of the - server. - -3) kickall - Kick EVERYONE connected to the server. - -4) kick [ ...] - Kick a particular player (whose id is ). - Can specify more than one id. - example: 'kick 3294', 'kick 9492 2839 5138', 'kick 9327 4392', etc - -5) quit - Enter this to stop the server and quit. Recommended way of closing server. - -IF YOU WANT TO CLOSE THE SERVER WHILE IT IS RUNNING, ENTER "quit" IN SHELL. -TRYING TO FORCE CLOSE THE SERVER MAY GIVE UNEXPECTED RESULTS TO CLIENTS -CONNECTED. - -WHAT IS AN ID, WHAT IS A KEY? -============================= -When the clients connect to server, the server issues the client a unique -4-digit number as their 'ID' or 'key'. \ No newline at end of file diff --git a/pychess.py b/pychess.py index 8381d40..828bed4 100644 --- a/pychess.py +++ b/pychess.py @@ -30,9 +30,9 @@ # Coordinates of buttons in rectangle notation. sngl = (260, 140, 220, 40) -mult = (280, 200, 200, 40) -onln = (360, 260, 120, 40) -load = (280, 320, 200, 40) +mult = (260, 200, 200, 40) +onln = (260, 260, 120, 40) +load = (260, 320, 200, 40) pref = (0, 450, 210, 40) abt = (390, 450, 110, 40) hwto = (410, 410, 90, 30) @@ -81,16 +81,16 @@ def showMain(prefs): win.blit(MAIN.HEADING, (80, 20)) pygame.draw.line(win, (255, 255, 255), (80, 100), (130, 100), 4) pygame.draw.line(win, (255, 255, 255), (165, 100), (340, 100), 4) - win.blit(MAIN.VERSION, (345, 95)) + # win.blit(MAIN.VERSION, (345, 95)) win.blit(MAIN.SINGLE, sngl[:2]) win.blit(MAIN.MULTI, mult[:2]) win.blit(MAIN.ONLINE, onln[:2]) win.blit(MAIN.LOAD, load[:2]) - win.blit(MAIN.PREF, pref[:2]) - win.blit(MAIN.HOWTO, hwto[:2]) - win.blit(MAIN.ABOUT, abt[:2]) - win.blit(MAIN.STOCK, stok[:2]) + # win.blit(MAIN.PREF, pref[:2]) + # win.blit(MAIN.HOWTO, hwto[:2]) + # win.blit(MAIN.ABOUT, abt[:2]) + # win.blit(MAIN.STOCK, stok[:2]) # Initialize a few more variables cnt = 0 @@ -170,7 +170,7 @@ def showMain(prefs): if ret == 0: run = False elif ret != 1: - run = chess.online(win, ret[0], prefs, ret[1]) + run = chess.online(win, ret[0], ret[1], prefs, ret[1]) elif load[0] < x < sum(load[::2]) and load[1] < y < sum(load[1::2]): sound.play_click(prefs) diff --git a/requirement.txt b/requirement.txt new file mode 100644 index 0000000..478047f --- /dev/null +++ b/requirement.txt @@ -0,0 +1 @@ +python-chess \ No newline at end of file diff --git a/res/img/OIP.jpeg b/res/img/OIP.jpeg new file mode 100644 index 0000000..6cfd5d9 Binary files /dev/null and b/res/img/OIP.jpeg differ diff --git a/res/img/back.png b/res/img/back.png index 512da08..47b4cc4 100644 Binary files a/res/img/back.png and b/res/img/back.png differ diff --git a/res/img/findmatch.png b/res/img/findmatch.png new file mode 100644 index 0000000..433d41c Binary files /dev/null and b/res/img/findmatch.png differ diff --git a/res/img/home.png b/res/img/home.png new file mode 100644 index 0000000..47b4cc4 Binary files /dev/null and b/res/img/home.png differ diff --git a/res/img/homeome.jpeg b/res/img/homeome.jpeg new file mode 100644 index 0000000..a23b823 Binary files /dev/null and b/res/img/homeome.jpeg differ diff --git a/res/img/refresh.png b/res/img/refresh.png index 2ea33aa..4863e6c 100644 Binary files a/res/img/refresh.png and b/res/img/refresh.png differ diff --git a/res/log_game/a.txt b/res/log_game/a.txt new file mode 100644 index 0000000..8e43cd4 --- /dev/null +++ b/res/log_game/a.txt @@ -0,0 +1,10 @@ +h 2024-01-04-19-07 1 +h 2024-01-04-19-07 0 +a 2024-01-04-20-58 1 +a 2024-01-04-20-58 0 +h 2024-01-04-20-59 1 +a 2024-01-04-21-01 1 +a 2024-01-04-21-01 0 +h 2024-01-04-21-03 0 +h 2024-01-04-21-05 0 +h 2024-01-04-21-05 1 diff --git a/res/log_game/h.txt b/res/log_game/h.txt new file mode 100644 index 0000000..df1390f --- /dev/null +++ b/res/log_game/h.txt @@ -0,0 +1,9 @@ +a 2024-01-04-16-51 0 +a 2024-01-04-16-52 1 +a 2024-01-04-18-04 0 +a 2024-01-04-19-07 0 +a 2024-01-04-19-07 1 +a 2024-01-04-20-59 0 +a 2024-01-04-21-03 1 +a 2024-01-04-21-05 1 +a 2024-01-04-21-05 0 diff --git a/res/texts/login.txt b/res/texts/login.txt new file mode 100644 index 0000000..cbada48 --- /dev/null +++ b/res/texts/login.txt @@ -0,0 +1,2 @@ +First, please enter username & password + diff --git a/res/texts/online.txt b/res/texts/online.txt index 6bc3b6e..1b1511d 100644 --- a/res/texts/online.txt +++ b/res/texts/online.txt @@ -13,9 +13,3 @@ THE SECTION BELOW IS FOR SELF-HOSTED SERVERS Enter the server's address below and connect to it - - - -Advanced Settings: -Enter the IP protocol of server (IPv4 is default) - - IPv4 IPv6 diff --git a/server b/server new file mode 100755 index 0000000..f0a4935 Binary files /dev/null and b/server differ diff --git a/server.cpp b/server.cpp new file mode 100644 index 0000000..065e83c --- /dev/null +++ b/server.cpp @@ -0,0 +1,1066 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LOG false +#define IPV6 false +struct PlayerSockInfo +{ + int sock; + int key; + // Thêm các thông tin khác của người dùng nếu cần +}; +struct PlayerInfo +{ + std::string username; + int id; + int score; +}; + +std::vector playerInfos; +std::queue logQ; +std::set busyPpl; +std::vector> players; +std::vector> findMatchPlayers; +std::vector> reMatchPlayers; +std::vector loggedUsernames; +bool end = false; +bool lock = false; +int total = 0; +int totalsuccess = 0; +const std::string VERSION = "v3.2.0"; +const int PORT = 26104; +std::chrono::time_point START_TIME; + +std::vector readPlayersFromFile(const std::string &filename) +{ + std::vector players; + std::ifstream file(filename); + std::string line; + + while (std::getline(file, line)) + { + std::istringstream iss(line); + PlayerInfo player; + iss >> player.username >> player.id >> player.score; + players.push_back(player); + } + + return players; +} + +void writePlayersToFile(const std::string &filename, const std::vector &players) +{ + std::ofstream file(filename); + + for (const auto &player : players) + { + file << player.username << " " << player.id << " " << player.score << "\n"; + } +} + +int makeInt(const std::string &num) +{ + try + { + return std::stoi(num); + } + catch (const std::invalid_argument &e) + { + return -1; + } +} + +std::string getTime() +{ + auto sec = std::chrono::duration_cast(std::chrono::steady_clock::now() - START_TIME).count(); + int minutes = sec / 60; + sec %= 60; + int hours = minutes / 60; + minutes %= 60; + int days = hours / 24; + hours %= 24; + std::stringstream ss; + ss << days << " days, " << hours << " hours, " << minutes << " minutes, " << sec << " seconds"; + return ss.str(); +} + +std::string getIp(bool publicIp) +{ + std::string ip; + if (publicIp) + { + try + { + std::string url = "https://api64.ipify.org"; + std::string response; + std::string command = "curl -s " + url; + FILE *pipe = popen(command.c_str(), "r"); + if (!pipe) + { + throw std::runtime_error("popen() failed!"); + } + char buffer[128]; + while (fgets(buffer, sizeof(buffer), pipe) != nullptr) + { + response += buffer; + } + pclose(pipe); + ip = response; + } + catch (const std::exception &e) + { + ip = "127.0.0.1"; + } + } + else + { + int sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock == -1) + { + ip = "127.0.0.1"; + } + else + { + sockaddr_in loopback; + std::memset(&loopback, 0, sizeof(loopback)); + loopback.sin_family = AF_INET; + loopback.sin_addr.s_addr = INADDR_LOOPBACK; + loopback.sin_port = htons(9); + if (connect(sock, reinterpret_cast(&loopback), sizeof(loopback)) == -1) + { + ip = "127.0.0.1"; + } + else + { + sockaddr_in name; + socklen_t namelen = sizeof(name); + if (getsockname(sock, reinterpret_cast(&name), &namelen) == -1) + { + ip = "127.0.0.1"; + } + else + { + ip = inet_ntoa(name.sin_addr); + } + } + close(sock); + } + } + return ip; +} + +void log(const std::string &data, int key = -1, bool adminput = false) +{ + std::string text; + if (adminput) + { + text = ""; + } + else if (key == -1) + { + text = "SERVER: "; + } + else + { + text = "Player " + std::to_string(key) + ": "; + } + if (!data.empty()) + { + text += data; + if (!adminput) + { + std::cout << text << std::endl; + } + if (LOG) + { + std::time_t now = std::time(nullptr); + std::string time = std::asctime(std::localtime(&now)); + logQ.push(time.substr(0, time.length() - 1) + ": " + text + "\n"); + } + } + else + { + logQ.push(""); + } +} +bool isUsernameLoggedIn(std::string &username) +{ + return std::find(loggedUsernames.begin(), loggedUsernames.end(), username) != loggedUsernames.end(); +} + +int getKeyFromSock(int sock) +{ + + for (const auto &playerInfo : playerInfos) + { + if (playerInfo.sock == sock) + { + return playerInfo.key; + } + } + return -1; // hoặc giá trị khác để biểu thị không tìm thấy +} +// void writePlayersToFile(const std::string& filename, const std::vector& players) { +// std::ofstream file(filename); + +// for (const auto& player : players) { +// file << player.username << " " << player.id << " " << player.score << "\n"; +// } +// } +std::string getUsernameFromSock(int sock) +{ + std::string filename = "username.txt"; // Thay đổi tên file của bạn + std::vector players = readPlayersFromFile(filename); + int id = getKeyFromSock(sock); + for (auto &player : players) + { + if (player.id == id) + { + return player.username; + } + } + return ""; +} +std::string getUsernameFromKey(int key) +{ + std::string filename = "username.txt"; // Thay đổi tên file của bạn + std::vector players = readPlayersFromFile(filename); + for (auto &player : players) + { + if (player.id == key) + { + return player.username; + } + } + return ""; +} +int getEloByID(int id) +{ + std::string filename = "username.txt"; // Thay đổi tên file của bạn + std::vector players = readPlayersFromFile(filename); + for (auto &player : players) + { + if (player.id == id) + { + return player.score; + } + } + return 0; +} + +// Hàm để giới hạn số dòng trong file +void limitFileLines(const std::string &filename, int limit = 10) +{ + std::ifstream inputFile(filename); + if (inputFile.is_open()) + { + std::vector lines; + std::string line; + while (std::getline(inputFile, line)) + { + lines.push_back(line); + } + + inputFile.close(); + std::ofstream outputFile(filename); + int start = std::max(0, static_cast(lines.size()) - limit); + for (int i = start; i < lines.size(); ++i) + { + outputFile << lines[i] << "\n"; + } + outputFile.close(); + } +} + +void writeLog(std::string username_win, std::string username_lose) +{ + // 0 la thua, 1 la thang + // Win + std::string filelog_win = "./res/log_game/" + username_win + ".txt"; // Thay đổi tên file của bạn + + std::ofstream outputFile_win(filelog_win, std::ios::app); // Open in append mode + if (outputFile_win.is_open()) + { + auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + struct tm timeinfo; + localtime_r(&now, &timeinfo); + // Write log information + { + char buffer[80]; + strftime(buffer, sizeof(buffer), "%Y-%m-%d-%H-%M", &timeinfo); + outputFile_win << username_lose << " " << buffer << " " << 1 << "\n"; + } + outputFile_win.close(); + } + + // Lose + std::string filelog_lose = "./res/log_game/" + username_lose + ".txt"; + std::ofstream outputFile_lose(filelog_lose, std::ios::app); + if (outputFile_lose.is_open()) + { + auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + struct tm timeinfo; + localtime_r(&now, &timeinfo); + // Write log information + { + char buffer[80]; + strftime(buffer, sizeof(buffer), "%Y-%m-%d-%H-%M", &timeinfo); + outputFile_lose << username_win << " " << buffer << " " << 0 << "\n"; + } + outputFile_lose.close(); + } + // Giới hạn 10 dòng + limitFileLines(filelog_win); + limitFileLines(filelog_lose); +} +double calculateExpectedScore(int rating1, int rating2) +{ + return 1.0 / (1.0 + std::pow(10, static_cast(rating2 - rating1) / 400.0)); +} +void updateElo(int &rating, int opponentRating, int result) +{ + const double k = 32.0; // Hệ số K + double expectedScore = calculateExpectedScore(rating, opponentRating); + rating += static_cast(k * (result - expectedScore)); +} + +void score(int win, int lose) +{ + int id_win, id_lose; + int elo_win, elo_lose; + id_win = getKeyFromSock(win); + id_lose = getKeyFromSock(lose); + std::string filename = "username.txt"; // Thay đổi tên file của bạn + std::vector players = readPlayersFromFile(filename); + std::string username_win, username_lose; + for (auto &player : players) + { + if (player.id == id_win) + { + // player.score += 10; + elo_win = player.score; + username_win = player.username; + break; + } + } + for (auto &player : players) + { + if (player.id == id_lose) + { + username_lose = player.username; + // if (player.score < 5) + // { + // break; + // } + // player.score -= 5; + elo_lose = player.score; + break; + } + } + updateElo(elo_win, elo_lose, 1); // Người thắng + updateElo(elo_lose, elo_win, 0); // Người thua + + writeLog(username_win, username_lose); + + for (auto &player : players) + { + if (player.id == id_win) + { + player.score = elo_win; + } + else if (player.id == id_lose) + { + player.score = elo_lose; + } + } + writePlayersToFile(filename, players); +} +std::string read(int sock, int timeout = -1) +{ + char buffer[9]; + std::memset(buffer, 0, sizeof(buffer)); + // if (timeout != -1) + // { + // timeval tv; + // tv.tv_sec = timeout; + // tv.tv_usec = 0; + // setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&tv), sizeof(tv)); + // } + ssize_t bytesRead = recv(sock, buffer, sizeof(buffer) - 1, 0); + if (bytesRead <= 0) + { + return "quit"; + } + std::cout << sock << ": " << buffer << std::endl; + return std::string(buffer); +} + +void longWrite(int sock, const std::string &msg) +{ + ssize_t bytesSent = send(sock, msg.c_str(), msg.length(), 0); +} +void write(int sock, const std::string &msg) +{ + std::string buffedmsg = msg + std::string(1024 - msg.length(), ' '); + ssize_t bytesSent = send(sock, buffedmsg.c_str(), buffedmsg.length(), 0); +} +std::string remove_space(std::string string) +{ + string.erase(std::remove(string.begin(), string.end(), ' '), string.end()); + return string; +} +int genKey(std::string username) +{ + std::ifstream file("username.txt"); + std::string line; + while (std::getline(file, line)) + { + std::istringstream iss(line); + std::string stored_username; + int stored_id; + iss >> stored_username >> stored_id; + if (username.compare(stored_username) == 0) + { + return stored_id; + } + } + return 0; +} + +int getByKey(int key) +{ + for (const auto &player : players) + { + if (player.second == key) + { + return player.first; + } + } + return -1; +} + +void mkBusy(std::initializer_list keys) +{ + for (int key : keys) + { + busyPpl.insert(key); + } +} + +void rmBusy(std::initializer_list keys) +{ + for (int key : keys) + { + busyPpl.erase(key); + } +} + +int game(int sock1, int sock2) +{ + while (true) + { + std::string msg = read(sock1); + msg = remove_space(msg); + write(sock2, msg); + if (msg.compare("quit") == 0) + { + std::string tmp_username = getUsernameFromSock(sock1); + loggedUsernames.erase(std::remove(loggedUsernames.begin(), loggedUsernames.end(), tmp_username), loggedUsernames.end()); + return true; + } + else if (msg.compare("draw") == 0 || msg.compare("resign") == 0 || msg.compare("end") == 0 || msg.compare("lose") == 0 || msg.compare("win") == 0) + { + if (msg.compare("resign") == 0) + score(sock2, sock1); + else if (msg.compare("lose") == 0) + score(sock1, sock2); + return false; + } + } +} + +void player(int sock, int key) +{ + bool rematch = false; + while (true) + { + std::string msg = read(sock, 3); + msg = remove_space(msg); + + if (msg.substr(0, 4).compare("quit") == 0) + { + std::string tmp_username = getUsernameFromKey(key); + loggedUsernames.erase(std::remove(loggedUsernames.begin(), loggedUsernames.end(), tmp_username), loggedUsernames.end()); + return; + } + else if (msg.substr(0, 3).compare("his") == 0) + { + log("Made request for watch history.", key); + std::string username = getUsernameFromKey(key); + std::string filelog = "./res/log_game/" + username + ".txt"; // Thay đổi tên file của bạn + std::ifstream file(filelog); + std::string line; + int lineCount = 0; + + while (std::getline(file, line)) + { + lineCount++; + } + write(sock, "xnum" + std::to_string(lineCount)); + file.close(); + + // Process + std::ifstream file2(filelog); + while (std::getline(file2, line)) + { + write(sock, line); + } + file2.close(); + } + else if (msg.substr(0, 5).compare("pStat") == 0) + { + log("Made request for players Stats.", key); + std::vector> latestplayers = players; + std::set latestbusy = busyPpl; + if (latestplayers.size() > 0 && latestplayers.size() < 11) + { + write(sock, "enum" + std::to_string(latestplayers.size() - 1)); + for (const auto &player : latestplayers) + { + if (player.second != key) + { + int elo = getEloByID(player.second); + + // Them elo tai day + if (latestbusy.find(player.second) != latestbusy.end()) + { + write(sock, std::to_string(player.second) + "b" + std::to_string(elo)); + } + else + { + write(sock, std::to_string(player.second) + "a" + std::to_string(elo)); + } + } + } + } + } + else if (msg.substr(0, 2).compare("rg") == 0) + { + log("Made request to play with Player" + msg.substr(2), key); + int oSock = getByKey(std::stoi(msg.substr(2))); + if (oSock != -1) + { + if (busyPpl.find(std::stoi(msg.substr(2))) == busyPpl.end()) + { + mkBusy({key, std::stoi(msg.substr(2))}); + write(oSock, "gr" + std::to_string(key)); + write(sock, "msgOk"); + std::string newMsg = read(sock, 3); + newMsg = remove_space(newMsg); + if (newMsg.compare("ready") == 0) + { + log("Player " + std::to_string(key) + " is in a game as white"); + if (game(sock, oSock)) + { + return; + } + else + { + log("Player " + std::to_string(key) + " finished the game"); + } + } + else if (newMsg.compare("quit") == 0) + { + write(oSock, "quit"); + return; + } + rmBusy({key}); + } + else + { + log("Player " + std::to_string(key) + " requested busy player"); + write(sock, "errPBusy"); + } + } + else + { + log("Player " + std::to_string(key) + " sent invalid key"); + write(sock, "errKey"); + } + } + else if (msg.substr(0, 4).compare("gmOk") == 0) + { + + log("Accepted Player" + msg.substr(4) + " request", key); + int oSock = getByKey(std::stoi(msg.substr(4))); + write(oSock, "start"); + log("Player " + std::to_string(key) + " is in a game as black"); + if (game(sock, oSock)) + { + return; + } + else + { + log("Player " + std::to_string(key) + " finished the game"); + rmBusy({key}); + } + } + else if (msg.substr(0, 4).compare("find") == 0) + { + std::vector> findplayers; + int id_win = getKeyFromSock(sock); + findMatchPlayers.push_back({sock, id_win}); + int elo = getEloByID(id_win); + + // findMatchPlayers; + for (auto &player : findMatchPlayers) + { + int o_elo = getEloByID(player.second); + if (o_elo >= elo - 100 && o_elo < elo + 100) + { + findplayers.push_back(player); + } + } + + std::cout << findplayers.size() << std::endl; + // Tim nguoi co cung elo + + if (findplayers.size() == 1) + { + mkBusy({id_win, sock}); + write(sock, "Wait"); + } + else + { + // TODO: Tim 1 nguoi k ban de match + int current_player_index = -1; + int oSock = findplayers[0].first; + write(oSock, "xr" + std::to_string(key)); + write(sock, "msgOk"); + std::string newMsg = read(sock, 3); + newMsg = remove_space(newMsg); + if (newMsg.compare("ready") == 0) + { + findMatchPlayers.pop_back(); + findMatchPlayers.pop_back(); + std::cout << "number of players" << findMatchPlayers.size() << std::endl; + log("Player " + std::to_string(key) + " is in a game as white"); + if (game(sock, oSock)) + { + return; + } + else + { + log("Player " + std::to_string(key) + " finished the game"); + } + } + else if (newMsg.compare("quit") == 0) + { + write(oSock, "quit"); + return; + } + rmBusy({key}); + } + } + // Tim 1 nguoi trong danh sach + // Khong co thi doi trong 5p + // Neu co 1 nguoi cung dang doi => + + else if (msg.substr(0, 4).compare("gmNo") == 0) + { + log("Rejected Player" + msg.substr(4) + " request", key); + write(getByKey(std::stoi(msg.substr(4))), "nostart"); + rmBusy({key}); + } + } +} +void logThread() +{ + std::ofstream file("SERVER_LOG.txt", std::ios::app); + while (true) + { + std::this_thread::sleep_for(std::chrono::seconds(1)); + while (!logQ.empty()) + { + std::string data = logQ.front(); + logQ.pop(); + if (data.empty()) + { + return; + } + else + { + file << data; + } + } + } +} + +void kickDisconnectedThread() +{ + while (true) + { + std::this_thread::sleep_for(std::chrono::seconds(10)); + for (auto it = players.begin(); it != players.end();) + { + int sock = it->first; + int key = it->second; + char buffer[8]; + std::memset(buffer, '.', sizeof(buffer)); + ssize_t bytesSent = send(sock, buffer, sizeof(buffer), 0); + if (bytesSent > 0) + { + int cntr = 0; + int diff = 8; + while (true) + { + cntr++; + if (cntr == 8) + { + bytesSent = 0; + break; + } + if (bytesSent == diff) + { + break; + } + diff -= bytesSent; + bytesSent = send(sock, buffer, diff, 0); + } + } + if (bytesSent == 0) + { + log("Player " + std::to_string(key) + " got disconnected, removing from player list"); + it = players.erase(it); + } + else + { + ++it; + } + } + } +} + +// void adminThread() +// { +// while (true) +// { +// std::string msg; +// std::getline(std::cin, msg); +// log(msg, -1, true); +// if (msg.compare("report") == 0) +// { +// log(std::to_string(players.size()) + " players are online right now,"); +// log(std::to_string(players.size() - busyPpl.size()) + " are active."); +// log(std::to_string(total) + " connections attempted, " + std::to_string(totalsuccess) + " were successful"); +// log("Server is running " + std::to_string(std::thread::hardware_concurrency()) + " threads."); +// log("Time elapsed since last reboot: " + getTime()); +// if (!players.empty()) +// { +// log("LIST OF PLAYERS:"); +// for (size_t i = 0; i < players.size(); ++i) +// { +// if (busyPpl.find(players[i].second) == busyPpl.end()) +// { +// log(" " + std::to_string(i + 1) + ". Player" + std::to_string(players[i].second) + ", Status: Active"); +// } +// else +// { +// log(" " + std::to_string(i + 1) + ". Player" + std::to_string(players[i].second) + ", Status: Busy"); +// } +// } +// } +// } +// else if (msg == "mypublicip") +// { +// log("Determining public IP, please wait...."); +// std::string PUBIP = getIp(true); +// if (PUBIP == "127.0.0.1") +// { +// log("An error occurred while determining IP"); +// } +// else +// { +// log("This machine has a public IP address " + PUBIP); +// } +// } +// else if (msg.compare("lock") == 0) +// { +// if (lock) +// { +// log("Already in locked state"); +// } +// else +// { +// lock = true; +// log("Locked server, no one can join now."); +// } +// } +// else if (msg.compare("unlock") == 0) +// { +// if (lock) +// { +// lock = false; +// log("Unlocked server, all can join now."); +// } +// else +// { +// log("Already in unlocked state."); +// } +// } +// else if (msg.substr(0, 5).compare("kick") == 0) +// { +// std::istringstream iss(msg.substr(5)); +// std::vector keys; +// int key; +// while (iss >> key) +// { +// keys.push_back(key); +// } +// for (int k : keys) +// { +// int sock = getByKey(k); +// if (sock != -1) +// { +// write(sock, "close"); +// log("Kicking player" + std::to_string(k)); +// } +// else +// { +// log("Player " + std::to_string(k) + " does not exist"); +// } +// } +// } +// else if (msg.compare("kickall") == 0) +// { +// log("Attempting to kick everyone."); +// std::vector> latestplayers = players; +// for (const auto &player : latestplayers) +// { +// write(player.first, "close"); +// } +// } +// else if (msg.compare("quit") == 0) +// { +// lock = true; +// log("Attempting to kick everyone."); +// std::vector> latestplayers = players; +// for (const auto &player : latestplayers) +// { +// write(player.first, "close"); +// } +// log("Exiting application - Bye"); +// log(""); +// end = true; +// if (IPV6) +// { +// int sock = socket(AF_INET6, SOCK_STREAM, 0); +// sockaddr_in6 addr; +// std::memset(&addr, 0, sizeof(addr)); +// addr.sin6_family = AF_INET6; +// addr.sin6_port = htons(PORT); +// inet_pton(AF_INET6, "::1", &(addr.sin6_addr)); +// connect(sock, reinterpret_cast(&addr), sizeof(addr)); +// close(sock); +// } +// else +// { +// int sock = socket(AF_INET, SOCK_STREAM, 0); +// sockaddr_in addr; +// std::memset(&addr, 0, sizeof(addr)); +// addr.sin_family = AF_INET; +// addr.sin_port = htons(PORT); +// inet_pton(AF_INET, "127.0.0.1", &(addr.sin_addr)); +// connect(sock, reinterpret_cast(&addr), sizeof(addr)); +// close(sock); +// } +// return; +// } +// else +// { +// log("Invalid command entered ('" + msg + "')."); +// log("See 'onlinehowto.txt' for help on how to use the commands."); +// } +// } +// } + +bool checkusername(const std::string &username, const std::string &password) +{ + std::ifstream file("account.txt"); + std::string line; + while (std::getline(file, line)) + { + std::istringstream iss(line); + std::string stored_username, stored_password; + iss >> stored_username >> stored_password; + if (username.compare(stored_username) == 0 && password.compare(stored_password) == 0) + { + return true; + } + } + return false; +} + +void initPlayerThread(int sock) +{ + log("New client is attempting to connect."); + total++; + + std::string username = read(sock, 3); + username = remove_space(username); + if (username.empty()) + { + log("Error reading username from client."); + return; + } + std::string password = read(sock, 3); + password = remove_space(password); + if (password.empty()) + { + log("Error reading password from client."); + return; + } + while (!checkusername(username, password) || isUsernameLoggedIn(username)) + { + write(sock, "notOK"); + username = read(sock, 3); + username = remove_space(username); + password = read(sock, 3); + password = remove_space(password); + } + + write(sock, "OK"); + loggedUsernames.push_back(username); + if (read(sock, 3).compare("PyChess") == 0) + { + log("Client sent invalid header, closing connection."); + write(sock, "errVer"); + } + else if (read(sock, 3).compare(VERSION) == 0) + { + log("Client sent invalid version info, closing connection."); + write(sock, "errVer"); + } + else if (players.size() >= 10) + { + log("Server is busy, closing new connections."); + write(sock, "errBusy"); + } + else if (lock) + { + log("SERVER: Server is locked, closing connection."); + write(sock, "errLock"); + } + else + { + totalsuccess++; + int key = genKey(username); + playerInfos.push_back({sock, key}); + log("Connection Successful, assigned key - " + std::to_string(key)); + players.push_back({sock, key}); + write(sock, "key" + std::to_string(key)); + player(sock, key); + write(sock, "close"); + log("Player " + std::to_string(key) + " has Quit"); + players.erase(std::remove_if(players.begin(), players.end(), [sock, key](const std::pair &player) + { return player.first == sock && player.second == key; }), + players.end()); + rmBusy({key}); + } + close(sock); +} + +int main() +{ + int mainSock; + + log("Starting server with IPv4 (default) configuration."); + mainSock = socket(AF_INET, SOCK_STREAM, 0); + if (mainSock == -1) + { + perror("Error creating IPv4 socket"); + return 1; + } + + sockaddr_in addr; + std::memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(PORT); + addr.sin_addr.s_addr = INADDR_ANY; + + if (bind(mainSock, reinterpret_cast(&addr), sizeof(addr)) == -1) + { + perror("Error binding IPv4 socket"); + return 1; + } + + std::string IP = getIp(false); + if (IP == "127.0.0.1") + { + log("This machine does not appear to be connected to a network."); + log("With this limitation, you can only serve the clients "); + log("who are on THIS machine. Use IP address 127.0.0.1"); + } + else + { + log("This machine has a local IP address - " + IP); + log("USE THIS IP IF THE CLIENT IS ON THE SAME NETWORK."); + } + if (listen(mainSock, 16) == -1) + { + perror("Error listening for connections"); + return 1; + } + + log("Successfully Started."); + log("Accepting connections on port " + std::to_string(PORT)); + + // std::thread(adminThread).detach(); + std::thread(kickDisconnectedThread).detach(); + + if (LOG) + { + log("Logging is enabled. Starting to log all output"); + std::thread(logThread).detach(); + } + + while (true) + { + int s = accept(mainSock, nullptr, nullptr); + if (s == -1) + { + perror("Error accepting connection"); + return 1; + } + + if (end) + { + break; + } + + std::thread(initPlayerThread, s).detach(); + } + + close(mainSock); + return 0; +} + +// g++ server.cpp -o server && ./server \ No newline at end of file diff --git a/server.py b/server.py deleted file mode 100644 index 49ddafb..0000000 --- a/server.py +++ /dev/null @@ -1,437 +0,0 @@ -""" -This file is a part of My-PyChess application. -To run the online server, run this script. - -For more information, see onlinehowto.txt - -IMPORTANT NOTE: - Server.py needs atleast Python v3.6 to work. -""" - -import queue -import random -import socket -import threading -import time -from urllib.request import urlopen - -# These are constants that can be modified by users. Default settings -# are given. Do not change if you do not know what you are doing. -LOG = False -IPV6 = False - -#===================================================== -# DO NOT MODIFY ANYTHING BELOW THIS!! -#===================================================== - -# Define other constants -VERSION = "v3.2.0" -PORT = 26104 -START_TIME = time.perf_counter() -LOGFILENAME = time.asctime().replace(" ", "_").replace(":", "-") - -# Initialise a few global variables -busyPpl = set() -end = False -lock = False -logQ = queue.Queue() -players = [] -total = totalsuccess = 0 - -# Function to convert string to int. Doesnt raise errors, returns None instead. -def makeInt(num): - try: - return int(num) - except ValueError: - return None - -# A function to display elapsed time in desired format. -def getTime(): - sec = round(time.perf_counter() - START_TIME) - minutes, sec = divmod(sec, 60) - hours, minutes = divmod(minutes, 60) - days, hours = divmod(hours, 24) - return f"{days} days, {hours} hours, {minutes} minutes, {sec} seconds" - -# A function to get IP address. It can give public IP or private. -def getIp(public): - if public: - try: - ip = urlopen("https://api64.ipify.org").read().decode() - except: - ip = "127.0.0.1" - - else: - with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s: - try: - s.connect(('10.255.255.255', 1)) - ip = s.getsockname()[0] - except: - ip = '127.0.0.1' - return ip - -# A function to Log/Print text. Used instead of print() -def log(data, key=None, adminput=False): - global logQ - if adminput: - text = "" - elif key is None: - text = "SERVER: " - else: - text = f"Player{key}: " - - if data is not None: - text += data - if not adminput: - print(text) - - if LOG: - logQ.put(time.asctime() + ": " + text + "\n") - else: - logQ.put(None) - -# Used instead of sock.recv(), because it returns the decoded message, handles -# TCP packet loss, timeout and other useful stuff -def read(sock, timeout=None): - try: - sock.settimeout(timeout) - msg = sock.recv(8).decode("utf-8").strip() - - except: - msg = "quit" - - if msg: - return msg - return "quit" - -# A function to message the server, this is used instead of socket.send() -# beacause it buffers the message, handles packet loss and does not raise -# exception if message could not be sent -def write(sock, msg): - if msg: - buffedmsg = msg + (" " * (8 - len(msg))) - try: - sock.sendall(buffedmsg.encode("utf-8")) - except: - pass - -# Generates a random four digit number that is unique -def genKey(): - key = random.randint(1000, 9999) - for player in players: - if player[1] == key: - return genKey() - return key - -# Given a players key, returns the sock object for the player -# Returns None if player does not exist -def getByKey(key): - for player in players: - if player[1] == makeInt(key): - return player[0] - -# Makes the player(s) busy, ie puts the player's key in a list of busy people -def mkBusy(*keys): - global busyPpl - for key in keys: - busyPpl.add(makeInt(key)) - -# Makes the player(s) active, ie removes the player's key from the list of busy -def rmBusy(*keys): - global busyPpl - for key in keys: - busyPpl.discard(makeInt(key)) - -# This simple function handles the chess match. Returns after game ended. -# Returns True if player got disconnected during the match, false otherwise. -def game(sock1, sock2): - while True: - msg = read(sock1) - write(sock2, msg) - if msg == "quit": - return True - - elif msg in ["draw", "resign", "end"]: - return False - -# It handles every player's communiction with server. -def player(sock, key): - while True: - msg = read(sock) - if msg == "quit": - return - - elif msg == "pStat": - log("Made request for players Stats.", key) - latestplayers = list(players) - latestbusy = list(busyPpl) - - if 0 < len(latestplayers) < 11: - write(sock, "enum" + str(len(latestplayers) - 1)) - for _, i in latestplayers: - if i != key: - if i in latestbusy: - write(sock, str(i) + "b") - else: - write(sock, str(i) + "a") - - elif msg.startswith("rg"): - log(f"Made request to play with Player{msg[2:]}", key) - oSock = getByKey(msg[2:]) - if oSock is not None: - if makeInt(msg[2:]) not in busyPpl: - mkBusy(key, msg[2:]) - write(oSock, "gr" + str(key)) - - write(sock, "msgOk") - newMsg = read(sock) - if newMsg == "ready": - log(f"Player{key} is in a game as white") - if game(sock, oSock): - return - else: - log(f"Player{key} finished the game") - - elif newMsg == "quit": - write(oSock, "quit") - return - - rmBusy(key) - - else: - log(f"Player{key} requested busy player") - write(sock, "errPBusy") - else: - log(f"Player{key} Sent invalid key") - write(sock, "errKey") - - elif msg.startswith("gmOk"): - log(f"Accepted Player{msg[4:]} request", key) - oSock = getByKey(msg[4:]) - write(oSock, "start") - log(f"Player{key} is in a game as black") - if game(sock, oSock): - return - else: - log(f"Player{key} finished the game") - rmBusy(key) - - elif msg.startswith("gmNo"): - log(f"Rejected Player{msg[4:]} request", key) - write(getByKey(msg[4:]), "nostart") - rmBusy(key) - -# A thread to log all the texts. Flush from logQ. -def logThread(): - global logQ - while True: - time.sleep(1) - with open("SERVER_LOG_" + LOGFILENAME + ".txt", "a") as f: - while not logQ.empty(): - data = logQ.get() - if data is None: - return - else: - f.write(data) - -# This is a Thread that runs in background to remove disconnected people -def kickDisconnectedThread(): - global players - while True: - time.sleep(10) - for sock, key in players: - try: - ret = sock.send(b"........") - except: - ret = 0 - - if ret > 0: - cntr = 0 - diff = 8 - while True: - cntr += 1 - if cntr == 8: - ret = 0 - break - - if ret == diff: - break - diff -= ret - - try: - ret = sock.send(b"." * diff) - except: - ret = 0 - break - - if ret == 0: - log(f"Player{key} got disconnected, removing from player list") - try: - players.remove((sock, key)) - except: - pass - -# This is a Thread that runs in background to collect user input commands -def adminThread(): - global end, lock - while True: - msg = input().strip() - log(msg, adminput=True) - - if msg == "report": - log(f"{len(players)} players are online right now,") - log(f"{len(players) - len(busyPpl)} are active.") - log(f"{total} connections attempted, {totalsuccess} were successful") - log(f"Server is running {threading.active_count()} threads.") - log(f"Time elapsed since last reboot: {getTime()}") - if players: - log("LIST OF PLAYERS:") - for cnt, (_, player) in enumerate(players): - if player not in busyPpl: - log(f" {cnt+1}. Player{player}, Status: Active") - else: - log(f" {cnt+1}. Player{player}, Status: Busy") - - elif msg == "mypublicip": - log("Determining public IP, please wait....") - PUBIP = getIp(public=True) - if PUBIP == "127.0.0.1": - log("An error occurred while determining IP") - - else: - log(f"This machine has a public IP address {PUBIP}") - - elif msg == "lock": - if lock: - log("Aldready in locked state") - else: - lock = True - log("Locked server, no one can join now.") - - elif msg == "unlock": - if lock: - lock = False - log("Unlocked server, all can join now.") - else: - log("Aldready in unlocked state.") - - elif msg.startswith("kick "): - for k in msg[5:].split(): - sock = getByKey(k) - if sock is not None: - write(sock, "close") - log(f"Kicking player{k}") - else: - log(f"Player{k} does not exist") - - elif msg == "kickall": - log("Attempting to kick everyone.") - latestplayers = list(players) - for sock, _ in latestplayers: - write(sock, "close") - - elif msg == "quit": - lock = True - log("Attempting to kick everyone.") - latestplayers = list(players) - for sock, _ in latestplayers: - write(sock, "close") - - log("Exiting application - Bye") - log(None) - - end = True - if IPV6: - with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s: - s.connect(("::1", PORT, 0, 0)) - else: - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - s.connect(("127.0.0.1", PORT)) - return - - else: - log(f"Invalid command entered ('{msg}').") - log("See 'onlinehowto.txt' for help on how to use the commands.") - -# Does the initial checks and lets players in. -def initPlayerThread(sock): - global players, total, totalsuccess - log("New client is attempting to connect.") - total += 1 - - if read(sock, 3) != "PyChess": - log("Client sent invalid header, closing connection.") - write(sock, "errVer") - - elif read(sock, 3) != VERSION: - log("Client sent invalid version info, closing connection.") - write(sock, "errVer") - - elif len(players) >= 10: - log("Server is busy, closing new connections.") - write(sock, "errBusy") - - elif lock: - log("SERVER: Server is locked, closing connection.") - write(sock, "errLock") - - else: - totalsuccess += 1 - key = genKey() - log(f"Connection Successful, assigned key - {key}") - players.append((sock, key)) - - write(sock, "key" + str(key)) - player(sock, key) - write(sock, "close") - log(f"Player{key} has Quit") - - try: - players.remove((sock, key)) - except: - pass - rmBusy(key) - sock.close() - -# Initialize the main socket -log(f"Welcome to My-Pychess Server, {VERSION}\n") -log("INITIALIZING...") - -if IPV6: - log("IPv6 is enabled. This is NOT the default configuration.") - - mainSock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) - mainSock.bind(("::", PORT, 0, 0)) -else: - log("Starting server with IPv4 (default) configuration.") - mainSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - mainSock.bind(("0.0.0.0", PORT)) - - IP = getIp(public=False) - if IP == "127.0.0.1": - log("This machine does not appear to be connected to a network.") - log("With this limitation, you can only serve the clients ") - log("who are on THIS machine. Use IP address 127.0.0.1\n") - - else: - log(f"This machine has a local IP address - {IP}") - log("USE THIS IP IF THE CLIENT IS ON THE SAME NETWORK.") - log("For more info, read file 'onlinehowto.txt'\n") - -mainSock.listen(16) -log("Successfully Started.") -log(f"Accepting connections on port {PORT}\n") - -threading.Thread(target=adminThread).start() -threading.Thread(target=kickDisconnectedThread, daemon=True).start() -if LOG: - log("Logging is enabled. Starting to log all output") - threading.Thread(target=logThread).start() - -while True: - s, _ = mainSock.accept() - if end: - break - - threading.Thread(target=initPlayerThread, args=(s,), daemon=True).start() -mainSock.close() diff --git a/tools/loader.py b/tools/loader.py index 7eb8ea1..6e7d7df 100644 --- a/tools/loader.py +++ b/tools/loader.py @@ -28,13 +28,18 @@ BLACK = (0, 0, 0) GREEN = (0, 255, 0) RED = (200, 20, 20) +BLUE = (0, 0, 255) # Define a few constants that contain loaded texts of numbers and chararters. NUM = [vsmall.render(str(i), True, WHITE) for i in range(10)] LNUM = [small.render(str(i), True, WHITE) for i in range(10)] BLNUM = [small.render(str(i), True, BLACK) for i in range(10)] -SLASH = vsmall.render("/", True, WHITE) +CHARACTERS = [vsmall.render(chr(i), True, WHITE) for i in range(65, 91)] # Uppercase letters A-Z +CHARACTERS += [vsmall.render(chr(i), True, WHITE) for i in range(97, 123)] # Lowercase letters a-z +SLASH = small.render("/", True, WHITE) COLON = vsmall.render(":", True, WHITE) +HYPHEN = vsmall.render("-", True, WHITE) +SCOLON =small.render(":", True, WHITE) # This function displays a number in a position, very small sized text used. def putNum(win, num, pos): @@ -42,12 +47,24 @@ def putNum(win, num, pos): win.blit(NUM[int(i)], (pos[0] + (cnt * 9), pos[1])) # This function displays a number in a position, Small sized text used. -def putLargeNum(win, num, pos, white=True): - for cnt, i in enumerate(list(str(num))): - if white: - win.blit(LNUM[int(i)], (pos[0] + (cnt * 14), pos[1])) - else: - win.blit(BLNUM[int(i)], (pos[0] + (cnt * 14), pos[1])) +def putLargeNum(win, data, pos, white=True): + for cnt, char in enumerate(str(data)): + if char.isdigit(): + if white: + win.blit(LNUM[int(char)], (pos[0] + (cnt * 14), pos[1])) + else: + win.blit(BLNUM[int(char)], (pos[0] + (cnt * 14), pos[1])) + elif char.isalpha(): + # Check if it's an uppercase or lowercase letter + if 'A' <= char <= 'Z': + win.blit(CHARACTERS[ord(char) - 65], (pos[0] + (cnt * 14), pos[1])) + elif 'a' <= char <= 'z': + win.blit(CHARACTERS[ord(char) - 97 + 26], (pos[0] + (cnt * 14), pos[1])) + elif char == '-': + win.blit(SLASH, (pos[0] + (cnt * 14), pos[1])) + elif char == ':': + win.blit(SCOLON, (pos[0] + (cnt * 14), pos[1])) + # This function displays the date and time in a position on the screen. def putDT(win, DT, pos): @@ -233,6 +250,7 @@ class PREF: YES = small.render("YES", True, WHITE) NO = small.render("NO", True, WHITE) + class ONLINE: ERR = ( vsmall.render("Attempting to connect to server..", True, WHITE), @@ -246,20 +264,34 @@ class ONLINE: GOBACK = vsmall.render("Go Back", True, WHITE) EMPTY = small.render("No one's online, you are alone.", True, WHITE) + EMPTY_HISTORY = small.render("You have no matches.", True, WHITE) LOBBY = large.render("Online Lobby", True, WHITE) + HISTORY_TITLED = large.render("HISTORY", True, WHITE) LIST = medium.render("List of Players", True, WHITE) + HISTORY = small.render("HISTORY", True, BLUE) PLAYER = small.render("Player", True, WHITE) DOT = small.render(".", True, WHITE) ACTIVE = small.render("ACTIVE", True, GREEN) BUSY = small.render("BUSY", True, RED) - REQ = small.render("Send Request", True, WHITE) + + WIN_STATUS = small.render("WIN", True, GREEN) + LOSE_STATUS = small.render("LOSE", True, RED) + + REQ = small.render("Request", True, WHITE) YOUARE = medium.render("You Are", True, WHITE) ERRCONN = vsmall.render("Unable to connect to that player..", True, WHITE) + WAIT_FINDMATCH = vsmall.render("Waiting for other player..", True, WHITE) + REFRESH = pygame.image.load(os.path.join("res", "img", "refresh.png")) + FIND_MATCH = pygame.image.load(os.path.join("res", "img", "findmatch.png")) + + WAITING1 =( + vsmall.render("Please wait for the other player", True, WHITE), + ) REQUEST1 = ( vsmall.render("Please wait for the other player to", True, WHITE), @@ -273,7 +305,7 @@ class ONLINE: ) DRAW1 = ( - vsmall.render("Sent a request to your opponent for", True, WHITE), + vsmall.render("Sent a request to your opponent for_1", True, WHITE), vsmall.render("draw, wait for reply.", True, WHITE), ) @@ -281,6 +313,15 @@ class ONLINE: vsmall.render("Your opponent is requesting for a", True, WHITE), vsmall.render("draw, please reply.", True, WHITE), ) + WIN1 = ( + vsmall.render("You Lose", True, WHITE), + vsmall.render("draw, wait for reply.", True, WHITE), + ) + + WIN2 = ( + vsmall.render("Your Win", True, WHITE), + vsmall.render("draw, please reply.", True, WHITE), + ) POPUP = { "quit": vsmall.render("Opponent got disconnected", True, WHITE), @@ -288,10 +329,19 @@ class ONLINE: "draw": vsmall.render("A draw has been agreed", True, WHITE), "end": vsmall.render("Game ended, opponent left", True, WHITE), "abandon": vsmall.render("Opponent abandoned match", True, WHITE), + "win": vsmall.render("You win", True, WHITE), + "lose": vsmall.render("You lose", True, WHITE), + } NO = small.render("NO", True, WHITE) OK = small.render("OK", True, WHITE) +class LOGINBOARD: + HEAD = large.render("Login", True, WHITE) + with open(os.path.join("res", "texts", "login.txt")) as f: + TEXT = [vsmall.render(i, True, WHITE) for i in f.read().splitlines()] + + CONNECT = small.render("Connect", True, WHITE) class ONLINEMENU: HEAD = large.render("Online", True, WHITE) diff --git a/username.txt b/username.txt new file mode 100644 index 0000000..1225902 --- /dev/null +++ b/username.txt @@ -0,0 +1,4 @@ +hoan 1102 452 +ky 2212 542 +h 1010 618 +a 1100 607