We're sorry but this page doesn't work properly without JavaScript enabled. Please enable it to continue.
Feedback

Getting 1K Chess for the ZX81 online

00:00

Formale Metadaten

Titel
Getting 1K Chess for the ZX81 online
Untertitel
Or, how I used $2 Billion of internet infrastructure to run 672 bytes of code, from 1982
Serientitel
Anzahl der Teile
287
Autor
Lizenz
CC-Namensnennung 2.0 Belgien:
Sie dürfen das Werk bzw. den Inhalt zu jedem legalen Zweck nutzen, verändern und in unveränderter oder veränderter Form vervielfältigen, verbreiten und öffentlich zugänglich machen, sofern Sie den Namen des Autors/Rechteinhabers in der von ihm festgelegten Weise nennen.
Identifikatoren
Herausgeber
Erscheinungsjahr
Sprache

Inhaltliche Metadaten

Fachgebiet
Genre
Abstract
In 1982, David Horne wrote a version of chess which managed to fit inside the memory of a 1K ZX81. Although it wasn't a complete implementation, it was impressive enough to be remembered now, almost 40 years later. But running it in 2022 requires at least an emulator, ROM file, and the .P file, which limits the appeal to retro enthusiasts and excludes the intended audience - chess players! So, I modified the EMF emulator (and the game) to run as an emulator-as-a-service, so that it can be played via the lichess servers with a modern web-friendly interface. In this talk I'll cover a brief history of the program, how the emulator was modified, how the program was reverse engineered and understood, how this code connects to the lichess server and, most crucially, why someone would do it! Along the way we'll look at the chess notation, FEN, Z80 assembler, NDJSON, and a few other pieces to show you the complete puzzle fits together.
SpieltheorieSoftwareentwicklerOpen SourceMobiles InternetNeuroinformatikCodeEmulatorComputerschachProgrammiergerätSpieltheorieMereologieProgrammierungMagnetbandkassetteMultiplikationsoperatorSoftwareRechter WinkelHalbleiterspeicherPhysikalismusComputerspielQuellcodeVerschlingungComputervirusProdukt <Mathematik>FehlermeldungRechenschieberBitAssemblerTypentheorieRPCSystemplattformQuick-SortBildschirmmaskeVideokonferenzBesprechung/Interview
HalbleiterspeicherProgrammierungTouchscreenVirtuelle MaschineMultiplikationsoperatorHalbleiterspeicherVariableMinkowski-MetrikSystemaufrufProgrammiergerätDatensichtgerätPhysikalisches SystemBesprechung/Interview
AuswahlverfahrenGemeinsamer SpeicherValiditätProgrammierungComputerschachCodeZusammenhängender GraphAuswahlaxiomSpieltheorieVirtuelle MaschineVorlesung/Konferenz
Wiederkehrender ZustandLokales MinimumGewicht <Ausgleichsrechnung>CodeComputerschachTouchscreenSchlussregelQuellcodeWeb-SeiteBesprechung/Interview
WhiteboardGraphikprozessorSpieltheorieKraftComputerschachHalbleiterspeicherSpielkonsoleDisassemblerWhiteboardCLIServerSechseckSpieltheorieDebuggingMaschinencodeSpielkonsoleSchreiben <Datenverarbeitung>EmulatorVersionsverwaltungBrowserBildschirmfensterFunktionalHalbleiterspeicherCodeNeuroinformatikAggregatzustandBefehlsprozessorStrömungsrichtungBesprechung/Interview
WhiteboardHalbleiterspeicherGrenzschichtablösungSelbstrepräsentationIdentitätsverwaltungOrtsoperatorASCIIPhysikalisches SystemBefehlsprozessorMereologieTouchscreenDatenstrukturWhiteboardSpeicheradresseNeuroinformatikBesprechung/Interview
Zellularer AutomatHalbleiterspeicherW3C-StandardDatenverwaltungMaschinencodeWhiteboardEin-AusgabeBefehlsprozessorEmulatorSpieltheoriePatch <Software>EreignishorizontDatenstromDesintegration <Mathematik>PunktMereologieVirtuelle MaschineAggregatzustandDifferenteBildschirmmaskeAnalytische FortsetzungHackerIntegralServerStreaming <Kommunikationstechnik>CodeProgrammierungHook <Programmierung>URLQuellcodeZeichenketteSpeicheradresseHalbleiterspeicherRoboterVersionsverwaltungSchlüsselverwaltungSystemaufrufEmulatorSpieltheorieAssemblerProgrammcodeZahlenbereichProgrammierumgebungWhiteboardMailing-ListeNeuroinformatikCASE <Informatik>ZahlensystemDemo <Programm>DatenstromMathematikMultiplikationsoperatorAdressraumWärmeübergangTouchscreenBesprechung/Interview
StatistikStreaming <Kommunikationstechnik>EreignishorizontSpieltheorieObjekt <Kategorie>DatentypComputerschachGraphfärbungStreaming <Kommunikationstechnik>SpieltheorieInformationTypentheorieEindringerkennungBitrateRoboterComputeranimation
Objekt <Kategorie>InformationStreaming <Kommunikationstechnik>ComputerschachSpieltheorieMultifunktionMailing-ListeBootenDatenstrukturSpieltheorieEmulatorPunktComputeranimation
Objekt <Kategorie>InformationSpieltheorieMultifunktionWhiteboardSpywareMaschinencodeBefehlsprozessorEmulatorHalbleiterspeicherSpieltheorieDatenstromWhiteboardMessage-PassingCodeVersionsverwaltungSpeicheradresseBefehlsprozessorZahlensystemTouchscreenSystemaufrufMultiplikationsoperatorProgrammierungGeradeCASE <Informatik>Computeranimation
Objekt <Kategorie>SpieltheorieBefehlsprozessorWhiteboardMaschinencodeSpywareDebuggingQuellcodeServerProzess <Informatik>QuellcodeCodeRoboterComputerschachVerschlingungMultiplikationsoperatorDebuggingTwitter <Softwareplattform>WhiteboardAggregatzustandNormalvektorAssemblerNeuroinformatikBitValiditätBesprechung/Interview
Computeranimation
Transkript: Englisch(automatisch erzeugt)
Hello. As this part of the slide says, my name is Steve, and as this part of the slide says, I'm a bit of a geek. Now, if we've all pressed play on the right video, this is a session about the ZX81 program 1K Chess.
Over the next 20 minutes or so, I'm going to be talking about the origin of this game, how it came to be, why is it so important, 20 years after it was written. I'm going to talk about how I rework the code from Z80 Assembler into JavaScript and Node, so that it will run as an emulator as a service.
I'll talk about connecting that Z80 game to a remote chess service, in this case, YHS, and how the two can work together. And finally, I'll ask the big question, why did I do it? But before all of that, who am I? What have I done to earn a face on this video screen?
Or, as perhaps the slide should be called, the ego slide. Well, I'm a developer. I've always been a developer. I've written games and software for quite a few platforms over the years, and it's always been sort of the modern stuff. When Xbox was trendy, I was writing for Xbox. When GameCube was new, I was writing for GameCube, and so forth. But I've always been a retro enthusiast.
I still have a collection home of various machines, and I still use them in their physical form as well as the emulated form. As a consequence, I've been asked to write a crowdfunding book, 20 Go To 10, about these old computers and how they work. And I helped out the Computer Museum in Cambridge.
But what's important is what's not on the slide. It's what's not on the slide. I'm not a professional Z80 programmer. I'm not a professional researcher looking into the political importance of computer games from the 1980s. Everything I've done here has been because I find it interesting. Which is a very long-winded way of saying, if I can do it, anyone can do it.
So let's get started. This 1K chess program, it was written by a guy called David Horn back in 1981-82, released in 82 on Arctic, which was quite a big label at the time for a lot of Sinclair software especially. And it worked so well that Sinclair bought the rights from Arctic to be able to sell it under the Sinclair brand.
The ZX81 only had 1K of memory, so any software they could get was pretty much a good thing. Also, unusually for the time, and even unusually now, that commercial program was released as source code.
We're used to open source, always being open, but back then, the code for commercial products was always commercial. It was always proprietary. In this case, the source was actually released. It was actually printed in magazines. Specifically this magazine in 1982, in December, the first part of the chess program was published as source code.
And it showed how two players could play chess against each other. A month later, in January 83, David updated the program to show how the moves could be validated by a piece of program code. And then in February 83, he showed how he could add some AI into the system so that a human could play against a machine, and still all in 1K of memory.
Now, there was no reason why you couldn't just buy the magazines and type the programming yourself. But as people of this era will know, typing in programs for magazines didn't always work as well as advertised. So you might as well have just paid your 5 quid and bought the cassette.
Yeah, sorry, you had to buy a physical cassette and, like, you know, you couldn't download it with a GitHub link. Sorry, wrong error. So what did you get for your 1K of memory? Well, the 1Kabyte in the ZX81 gives you most of it to the programmer. There is some space that is used by the system variables. There's some space which is required by the stack, either for the calculating stack or for the program call stack.
And there is memory used for the screen. A lot of machines at this time would use the same memory for the video display and the program and the data and so forth. So the ZX81, with only 1K of memory, did a very neat trick.
It didn't require a lot of memory to have the screen. But if you wanted something on the screen, that would cost you. So a blank screen would cost you, say, 23 bytes, whereas a full screen would cost you 768. Consequently, this is why the display is always at the top left, because it uses the least amount of memory.
I estimate about 123 bytes, just for the video display here. So obviously with 1K of memory, there are going to be some limitations. Firstly is the CPU will always move forward. When you load the program from Tait, it was saved in such a way that the first move has already been made by the machine.
And that move is Queen-Pawn-Forward. There was a second program on the Tait, which was King-Pawn-Forward, so you have a choice of two games. Also, there is no castling. The AI does not know how to castle, and it doesn't consider castling a valid move from the human. There is also no ability to promote your pawn to anything other than the Queen.
So when your pawn gets to the far end of the board, it automatically becomes a Queen, and does not have a component which says which piece would you like. Now, for a lot of beginner chess players, you always promote the Queen. Why wouldn't you? It's the best piece. But in intermediate and advanced games, promoting to a Queen can force a stalemate when you don't really want one.
So you would like the ability to support promotion to Rook, for example. And this UI and this AI both share the same valid move code. And this comes important later, as there's a subtlety we'll discover. And if you are wondering what one kilobyte of chess code looks like, it's that.
Now, you're not expected to be able to read all about this resolution, but suffice to say, the entire code fits on the screen. The rules of chess would not even fit on the screen. That's how compact it is. And if you're curious about what it would look like, a source code, and pretty printed, it's this.
Also, you're not expected to be able to read that, even if I zoom in, it's quite tricky. But suffice to say, it fits on a page. So, at the high level, what do we need to do? Well, first off, the Lytra server will give us the current board game. It says, these are the pieces on the board, this is where they sit,
off you go, make a move. Next, we need to inject that board into the emulated version of the game. We'll find out how we do that shortly. We then need to force the CPU to make a move. Once it's made that move, we need to determine that it's finished making the move,
and then read that move back, or read the board state back, whichever happens to be appropriate, and then send that new move and or board state back to the Lytra server so that it can ask the human for its move. And we repeat this loop over and over again until the game ends. And naturally, we have to detect that in some way as well.
So, obviously, as you can guess, it's retro, there's going to be an emulator, and the emulator I use is EMF. That's not necessarily because it's the best emulator, but it's the only one I've written. It's also written in JavaScript, which is very handy for two reasons. It comes with two debuggers. This is the first debugger, this is the basic EMF debugger.
You can see there is the disassembly, there are the registers from the CPU, there is memory dumps, and there is also down here a little CLI where you can read and write memory, you can watch memory being changed, and you can add breakpoints and other things like that. You can rewrite the code on the fly as well in there. You write the assembler, it works out what the hex codes would be,
and it writes it into memory. That's one debugger. The second debugger is the one that comes in the web browser itself. So the browser console window allows you to add even more things, so you can add callbacks to JavaScript functions that do additional work. That also becomes handy. So, there are three things we need to know. Where is the board in memory?
Where in code does the human move? Because the human doesn't need to move on the emulated version anymore, that's the human moves in my chest. And when has the computer finished moving? So the first part is where is this board in memory? Well, it's there. Address 17,213.
This memory address is the screen itself. There is no separate data structure that the AI holds about the representation of the board versus what the screen holds. They're identical. So if you change the memory on the screen, you change the board position that the CPU then uses to work out its next move.
That becomes handy later on. So, it's all essentially about rewriting that screen memory. And the computer will play from whatever position is on there. Which makes it very easy for us. If we want to determine what's on the board, we simply read across each memory and then convert the value in that memory address which is the character code,
into a piece. Now the astute amongst you will notice that Bishop begins with B and the ASCII for B is certainly not 39. That's because the ZX81 doesn't use ASCII. It uses a separate character coding system for which you have to go back to the original manual and look up the numbers. It's easily done.
Second point is where does the human move? This is quite straightforward. When you load the program, the first thing that happens is the human gets to move. So it's pretty much the first thing that's ever run. 16,514. And the listing that you can see here, which is taken from the magazine, shows this to be true. So it's very simple.
Now unfortunately this assembler is pretty poor. As you can see, call NN. That should actually be the actual number. And you'll also notice the numbers are already decimal. As an assembler programmer, I normally like to think in hex, so it's actually a little more painful to use the source code than you'd like. But it's there, so I'm not going to complain.
So all we need to do is we need to say, well, the human doesn't need to move in the emulated version again. The human moves in light chest over there. So let's just remove that code. Then we call the computer to make its AI move. Because now when we start up the game, the human has already moved. And then we just return from that routine
because we say, we're done. And that's it. The third case is when has the computer moved? Well, we do this by finding an instruction, which is a call instruction to 42F7, which is an address in memory for the move has happened.
This happens twice. Once when the computer finishes its move, and it transfers play to the human, and again when the human has finished its move, and play transfers to the machine. And what happens is 1 byte in memory changes. It goes from 0 to 128 every time the player changes. And this particular memory
address is there. The variable is on screen. It's part of the video memory again. So we actually want to remove this. Because when the AI is running, when the emulator is running, it's only ever the machine's turn. So we remove that code. But we also know that if it ever gets called,
it's by the machine. And therefore, we can switch back out to say, okay, here is the move the machine has made. Give it the light chest server, and let everyone continue. So it becomes quite a very simple piece of hacking. After the emulator has loaded the code, we just rewrite parts of its game code, essentially with JavaScript
oriented breakpoints that says, when the program counter hits this address, go to some JavaScript, and the JavaScript will evaluate the state of play, and do whatever it needs to do to fulfill its obligations. From here, we can look at the light chest integration. And most of this is JavaScript and Node code, which will only interest, I'm sure, a few of you, so I'll
run through it as quickly as I dare. So first off, we convert the emulator from JavaScript into Node. Few minor differences there, nothing important. The big things to notice is that the binary data of the ZX81 ROM and the binary data of the program code are converted to ASCII in some form so they can be loaded in. We then get an account on the light chest server.
We then have to say, I want this account to run bots. And once it becomes a bot account, you can't switch back to humans. As a bot account, you can get an API key. This obviously has the same modifications of all other API keys. It's yours, keep it safe. You then hook into the light chest data stream.
For this, you make a GET request to our URL on the light chest server, and you get back in the JSON. This is newline delimited JSON. So you get a string of JSON ending with a carriage return, but not an end of file, which means the stream is kept open, which means the server
can keep sending you new commands without having to open a new request, essentially allowing the server to push data to you, even though your machine might be behind a firewall, for example. It might have a private IP somewhere, because the only thing it ever does is call out from your server to light chest. Light chest
never calls into you. So it's a nice way of solving the tricky problem. It also makes it very easy if you're moving your bot from one server to another, because if you've got the same token, you get the same environment. You don't have to upload a new API endpoint to light chest. And then you just set up callbacks for
each of those pieces of NDJSON. When you first request the data stream, it tells you what games are currently in play, and you may then go and react to those games, make a move. It will then make a request to you when there is a new challenge, if someone wants to start a game with the bot, it will say, so and so would like to play a game, they wish to
play as black or as white. Now I try to be authentic here, because the original game only let you play as black, so I only let you play as black in the light chest version. And also, if a move is being made by the player, it will send you via NDJSON a get move. It will send you the FEN, which is the Foresight-Edwards notation
for chess, it describes the board. We then take that FEN notation, we convert it into a binary, which we then poke into the emulator memory. And that's essentially it, so I should probably show you a demo. So here I'm running my bot code, it starts by getting up the stream, and it gets the game list
which is returned automatically. Now we've started up a challenge here, so the first thing that comes in, as you can see, it says stream data gets this type challenge, it gives you all the information, it gives IDs for the game on the light chest server, which person wants to play the game in case, that's me, Mark Kristi Geek, the rating of the player, 1500
which is average and boring, and the destination user which is us, the bot, zx-chess. There's a few other pieces of information here, such as the color you're performing, and things which we're not really worried about, other bots might use them, we don't. We've passed that, we can tell it's a challenge, and we've also got the information to say
that the game has been requested to start. With it starting, we then initiate the emulator, it boots up, it knows that on the game start, it's going to be a Queen Pawn move, because that's the only one the original game would make. So the emulator then makes that move. We have this structure that you can see the FEN in there, which describes the board,
and the last move. We can then send that move, that E2 to E4 move back to the light chest server, at which point it's the human's turn to move. Now all this time the emulator is not actually running. Nothing happens until the player makes a move. So we just wait there, we've made a get request to that data stream, the
NDJson is not doing anything, we make the move, we shuffle our Pawn forward for example, and when we do that data stream returns another line of NDJson that says there was a move. In our case it says that our move was E7 to E5, that Pawn, it gives us the FEN for the new board. We
then convert that into the notation you see, it's a nice little ASCII notation which is easy to read. You'll notice that it's reversed to what it is on the screen. This is because in our nice easy notation, white is bottom left, it's on the lower half of the screen, but in the graphical light chest version
white is at the top. So we have been given a board, we decode that board into the notation, we start the emulator but we don't run it yet. We then rewrite the code in the emulator to put those knops and the call instructions as we need them. We rewrite the memory to include this game
board that we've received from light chest. We then start the emulator running. We can then see that the CPU is moving. This we know because we've got those breakpoints in, when the program counter hits particular memory locations it calls JavaScript and our JavaScript writes out a message. We then wait, the CPU
moves, it works out the move which is appropriate and bingo. It makes a new move and sends it back to the server. And the process begins again. Next up, we come to that big question. Why? Why would I spend all this time and effort trying to run 672 bytes of Z80 assembler
from 40 years ago on a chess AI that isn't even that good? The reason is quite simple. Why did... Sorry, I've run out of time. In conclusion, I'll just say that JavaScript gives you two debuggers which is really really handy. Having the Z80 source was helpful
but because of the whole use of decimal rather than hex it does cause a little bit of consternation and it does... using Lychess lets you overcome the UI. Remember that the valid move code was used by both the AI and the human. So if Lychess says oh you can castle if you like
all we're doing is plugging that new board state into 1K chess and the AI will just run it as normal. Now this means that the player can use en passant and the player can use castling and the player can use promotion to rook or to knight or something and the AI can't. So it gives you an advantage. But with this AI
you really don't need one. Seriously. Even I can beat the chess. So with that I will drop back to the chat and answer any questions. Before I do I will update my FOSDEM scorecard. If you're interested about the 2020goto10 book that I mentioned earlier that I'm writing on retrocomputers then that's the link. If you want to read the code for the chess bot then that's the link. If you're watching
this at a time which isn't FOSDEM then the Twitter link I might still be there if they haven't banned me yet for talking about computers. So I'll leave it there. Thank you very much and I shall see you later.