Cqsi / lichs

♟ Play chess against real players in your terminal using Lichess
https://pypi.org/project/lichs/
MIT License
114 stars 19 forks source link

Play against AI problem. #40

Closed 9acca9 closed 3 years ago

9acca9 commented 3 years ago

Hello. I'm trying to make a match against the lichess AI. using berserk this can be generated,

reto = berserk.clients.Challenges(session) 
(...)
reto.create_ai(level=6, clock_limit=None, clock_increment=None, days=None, color="black", variant=None, position=None)

but I have a problem ... If I play with white against the AI everything works great. But, if the AI plays with white the code is broken ... this happens because the AI has neither "user" nor "id". if player_id != client.games.export(event['game']['id'])['players']['white']['user']['id']:

Therefore, I thought about eliminating those fields (just to test ...), the error is not generated there but the AI move does not reach me. The AI moves for example e2e4 ... and it never reaches me. In fact, the AI movement only comes when I enter the website and cancel the game.

Thanks for your time.

This is obviously something that is not in your program but I ask you since you worked with this a lot, you may notice where the problem could be.

9acca9 commented 3 years ago

It seems that the AI plays immediately and when following the flow of the program, there is already a movement, therefore the functions "lost" it.

for event in board.stream_incoming_events():
        print("ESto es todo lo que viene en event: ",event)
        if event['type'] == 'gameStart':

            print( client.games.export(event['game']['id'])) # Here already a move from the AI...
            print("An opponent was found!")

{'id': 'xxxxx', 'rated': False, 'variant': 'standard', 'speed': 'correspondence', 'perf': 'correspondence', 'createdAt': datetime.datetime(2021, 3, 20, 1, 56, 58, 136000, tzinfo=datetime.timezone.utc), 'lastMoveAt': datetime.datetime(2021, 3, 20, 1, 57, 0, 184000, tzinfo=datetime.timezone.utc), 'status': 'started', 'players': {'white': {'aiLevel': 6}, 'black': {'user': {'name': 'xxxxx', 'id': 'xxxxx'}, 'rating': 1404, 'provisional': True}}, 'opening': {'eco': 'A00', 'name': 'Van Geet Opening', 'ply': 1}, 'moves': 'Nc3'}

There is the move... Nc3... even before start the object Game. For example when printing ... already from that position the first movement appears. And he didn't even go through any of the following. That is why when I play with White the error does not happen ... (because there it is "waited" for my move).

Any idea how i can catch this??

benediktwerner commented 3 years ago

Seems like lichs doesn't handle the gameFull event that is always sent as the first event when starting the stream and contains all moves played so far: https://github.com/Cqsi/lichs/blob/master/lichs/Game.py#L25

https://lichess.org/api#operation/boardGameStream

9acca9 commented 3 years ago

@benediktwerner Hi. Thanks. Could it be that this was a "problem" also related to Berserk? because in ...

for event in (board.stream_game_state(event['game']['id'])):
                    if event['type'] == 'gameFull':
                        print("Esto es event 1",event)

I get this kind of response:

{'id': 'TRxxxx41', 'variant': {'key': 'standard', 'name': 'Standard', 'short': 'Std'}, 'clock': None, 'speed': 'correspondence', 'perf': {'name': 'Correspondence'}, 'rated': False, 'createdAt': datetime.datetime(2021, 3, 20, 17, 25, 58, 418000, tzinfo=datetime.timezone.utc), 'white': {'aiLevel': 6}, 'black': {'id': 'xxxxxx', 'name': 'xxxxxxx', 'title': None, 'rating': 1404, 'provisional': True}, 'initialFen': 'startpos', 'type': 'gameFull', 'state': {'type': 'gameState', 'moves': 'g1f3', 'wtime': 2147483647, 'btime': 2147483647, 'winc': 0, 'binc': 0, 'wdraw': False, 'bdraw': False, 'status': 'started'}}

"gameFull" shouldn't be at the beginning ?? (However, it appears after "start pos") The order is relevant I guess. Thanks.

benediktwerner commented 3 years ago

Not sure what you mean with "it appears after start pos". The docs say the first event sent is always of type gameFull, like the one you posted. After that, you will probably only get gameState events but the docs don't say that you will never get another gameFull event. But if I read it correctly, it should at least sent a gameState event for every move played, when a draw is offered, or when the game ends.

9acca9 commented 3 years ago

HI. Sorry my ignorance. I mean this part: 'initialFen': 'startpos', 'type': 'gameFull', This "type: gameFull" i think would have to appear at the beginning of the message (at the beginning you have "id") and on lichess.api the order is this:

"type": "gameFull",
"id": "5IrD6Gzz",
"rated": true,
"variant": 
{},
"clock": 
{},
"speed": "classical",
"perf": 
{},
"createdAt": 1523825103562,
"white": 
{},
"black": 
{},
"initialFen": "startpos",
"state": 
{}

(anyway this probably does not nothing to do...). Either way I don't understand why everything works fine when playing against one person. I can create a challenge, I can accept a challenge and everything is ok. I can create a game against Ai if she plays black ... but ... I can't play against her if she plays white ... And yet when playing against Ai (Ai with white) things don't work. I am at a difficult point for me to advance. Why would I need the gameFull if I don't need it when playing against a person. Thanks.

benediktwerner commented 3 years ago

The keys in a JSON objects are unordered so which comes first doesn't matter and is essentially random. lichess might send them in a fixed order but when Python parses them it doesn't record the order and prints them however it wants.

As for why it works for humans and not vs AI: Simple: When you play vs an AI, it instantly makes the first move, before lichs can connect to the even stream API, so you never get the even for the move and only get the gameFull event. But a human will always at least take a little bit (even if it's just a few tens of milliseconds), which is enough for the even stream to start and allow receiving the event for the first move.

But that's exactly why lichess always sends the gameFull event at the start (besides the fact that it allows following games that have already been going on for a while and reconnecting to a game).

9acca9 commented 3 years ago

@benediktwerner OK thank you very much. So in code, I tend to think that one of the problems is the .https://github.com/Cqsi/lichs/blob/2cb68d925f10c3eab8c29129590b74b41a317926/lichs/Game.py#L16 or nothing to do with it? I've been thinking about this and I guess this is part of the problem? Or is the whole problem on the line you marked for me? I should add in the run something like if the event is gameFull ... I tend to think of the self.current_state the info is not adequate .....

benediktwerner commented 3 years ago

Hm, yeah, I guess the problem is kind of in both lines, on the one hand, self.current_state is never used and the first event is effectively lost, and on the other hand, the program never asks you for a move because it never handles the gameFull event at the line I linked.

I'd just remove this line and instead add some code to the line I linked to handle gameFull events. I guess even just changing run() to this might work (and removing line 16 as mentioned):

def run(self):
    for event in self.stream:
        if event['type'] == 'gameFull':
            self.handle_state_change(event['state'])
        elif event['type'] == 'gameState':
            self.handle_state_change(event)
        elif event['type'] == 'chatLine':
            self.handle_chat_line(event)

It's a bit hacky but the whole code is a big mess anyway.

Cqsi commented 3 years ago

@9acca9 Sorry that I didn't contact you earlier, I was a little busy. Hopefully you already solved your problem to some extent, I'm going to look into the problem soon.

Cqsi commented 3 years ago

Hm, yeah, I guess the problem is kind of in both lines, on the one hand, self.current_state is never used and the first event is effectively lost, and on the other hand, the program never asks you for a move because it never handles the gameFull event at the line I linked.

I'd just remove this line and instead add some code to the line I linked to handle gameFull events. I guess even just changing run() to this might work (and removing line 16 as mentioned):

def run(self):
    for event in self.stream:
        if event['type'] == 'gameFull':
            self.handle_state_change(event['state'])
        elif event['type'] == 'gameState':
            self.handle_state_change(event)
        elif event['type'] == 'chatLine':
            self.handle_chat_line(event)

It's a bit hacky but the whole code is a big mess anyway.

Thank you for helping him. I know the code is a quite a mess and I'm going to try to rewrite parts of it sometime. The main problem is that the whole idea of playing Lichess in a terminal wasn't really meant to be made with the Lichess API (berserk) and it's hard getting the correct usage out of it in order to get Lichs to work.

9acca9 commented 3 years ago

@benediktwerner Excellent!!! is working. I can play against the Ai, she playing with white!!! Now if I play white myself, an error occurs ... but it doesn't matter, I'll see how to solve it in a general way. Otherwise, then I will "activate" a code when I play with white and black respectively. (I'm particularly tired today to try). I really appreciate the time you gave me! Thanks!

@Cqsi No problem. Thanks to make this code. As you may have noticed, I am not a "programmer" but rather a hobbyist with still very little knowledge. Your program is very important to me, because I use it as the basis for "my electronic physical board" (I had written to you a while ago). But I also took the time to make the pieces by hand ... then some questions remained in the inkwell (does this expression exist in English? ... For example, this one about playing against Ai. Are you going to leave out Berserk? Are you going to format the json of lichess? Because that's what Berserk do right? Hope you keep improving your code.

Thanks to both!

Cqsi commented 3 years ago

@9acca9 @benediktwerner

Now I see! It's true that Lichs doesn't handle gameFull because I'm so sure it didn't exist when I wrote the program. See everything that's written below Line 41 https://github.com/Cqsi/lichs/blob/d2b56a29bdbab170135d47c1089a63c4f51c8f77/lichs/Game.py#L41 and a few lines after that? That's a complete mess! Of course I would have used the gameFull event to get the data about the players so I wouldn't have to manually determine which color is who etc. I'm going to change this as soon as I have time.

Concerning the rest of the code; I'm also going to take a look at the things @benediktwerner noted (the unused variables etc). @9acca9 I'm not going to leave out Berserk and I'm not really sure about the JSON file, I might look into that. Note however that if you come to the conclusion that there's a problem with Berserk (it might well be, I had some problems with it as well), remember that it's open-source so you can just download the code and edit and use the edited version of berserk in your program.

9acca9 commented 3 years ago

@Cqsi Hello. Ok, I had understood that you did not want to use Berserk because it was not the intention at first. (I saw that you even collaborated with him). On the other hand, in the Berserk github I had also asked him about it ... :-) And in one of my attempts I also tried to modify the Berserk code (when for a moment I thought that the order in which I saw the contents of the stream it was important). every time I see code, and ask about it I think I learn a little, little, little more. And I really like to play chess so it is a double motivation. Open-source is really impressive. It is a pity that I do not have the ability to generate something good to share. My girlfriend was reading Kropotkin and related it a lot to open-source because the main idea of ​​this author is the "mutual association" where everyone can contribute and benefit without practically limitations and without "expelling" anyone. And as everyone contributes willingly, and with enthusiasm impressive things are achieved.