The protocol is followed by the code used to implement it from Nemesis and Many Faces. We make this code available royalty free, for use for any purpose, commercial or otherwise.
NEMESIS and Many Faces will use this protocol in the upcoming North American Computer Go Championship at the Go Congress. We hope that other programs at this competition will also implement it.
Bruce volunteers to help with final testing of your modem protocol code.
David Fotland (Author of Many Faces of GO)
Some files for using the Go Modem Protocol in Windows can be downloaded from here.The protocol is maintained by Bruce Wilcox of Toyogo, Inc. If you need new codes (e.g., query, extended command) assigned, call or fax for them. In North America call toll-free 800-869-6469. Otherwise call 808-396-5526 or Fax 808-396-4126. Or write to: PO Box 25460, Honolulu, HI 96825
1. Start Byte 0000 00hy The start byte has 6 fixed bits, his sequence bit (h) and your sequence bit (y). The sequence bits determine if his incoming command is old or new, and whether he has seen or not seen your most recent command sent to him. Maximizing the fixed bits minimizes mistaken start byte recognition. If a new start byte is detected before the full 4 bytes of a previous packet are read, the old bytes are discarded. 2. Checksum Byte 1sss ssss Top bit always on. Checksum (s) of any message (including the variable length Extended command) is created by summing into unsigned int the 1st, 3rd and 4th message bytes, and using only the bottom 7 bits. If a program receives a command with a bad checksum, it should just discard the message. 3,4 Command 2-byte 1ccc rvvv 1vvv vvvv Top bit of each byte is always on. ccc is 3-bit command (8 basic commands). vvv vvv vvvv is a 10-bit value for use with commands. A reserved (r) bit of 0 separates the two fields. TEST FOR IT. Future commands or data or whatever may use it and break your code if you don't. Types are: 0. OK 1000 0111 1111 1111 Value must be all 1's. OK means I got your command ok, and is given whether or not the command is executed without error by recipient. This releases sender, who cannot transmit any new commands until he receives it. ANYTIME you have been sent a command and are expected to send an OK, you may instead send a command with a new sequence number for you and his current sequence number. This acts as an implicit OK, and a command to him which cannot be in conflict with a new command from him (see CONFLICT at the end of command list). This property is explicitly used in the Query-Answer protocol. DENYis an alternate response instead of OK. OK is not a true command. You do not send back an OK in response to OK. No response is needed or expected. You also do not send back OK in response to a QUERY. You must send an ANSWER command. 1. DENY command 1001 0000 1000 0000 If you wish to reject a command (can't do it or whatever), you can deny it by sending back the DENY command instead of an OK. He should treat your deny just as he would conflict, by retracting his command. However he does not revert his or your sequence ids. As a normal command, DENY is rebroadcast until he sends you an OK (explicit or implicit), and he will undo his command and stop broadcasting it to you whenever he sees your denial. Don't DENY a denial. If a command is part of a series of commands to be sent (not a part of an extended multiple command), the sender should cancel sending the remaining queued up commands. E.g., if a game record is being sent as a series of commands, sending it should stop as soon as a move is denied. If a denial is given on an extended multiple command, all of its subcommands must be undone. 2. NEWGAME command 1002 0000 1000 0000 Clear your board back to empty, and clear all query knowledge. The sender guarantees he has the correct conditions. A good (but not required) response to NEWGAME would be to use QUERY instead of OK, to discover the playing conditions. If you are unable to query, it is presumed the conditions are already set up correctly. As soon as the OK is complete (either to NEWGAME or to a series of queries followed by the final answer) Black may send his first move. It is important to wait for an OK, instead of embedding the first move command as an implicit ok, to allow both sides to complete any querying they want to do. If a DENY is received in the query process, the game is aborted. Whoever is BLACK should send the NEWGAME on startup, but the recipient should not assume from the receipt of NEWGAME that he is WHITE, but should use the query system instead or have been set up. [If two humans play, then it doesn't matter who initiated the NEWGAME, but if two computers are playing, you want the game to automatically start in an orderly fashion. Therefore, behavior on startup is that whoever is to be BLACK send the NEWGAME, and whoever is to be WHITE sends no commands until it is received (he may talk all he wants). ] NEWGAME can be sent in the middle of a game, in which case it terminates the game and starts another (game parameters determined by sender). 3. Query command 1011 0sqq 1qqq qqqq THE QUERY COMMAND IS OPTIONAL. YOU DO NOT HAVE TO MAKE A QUERY. The command asks a question, and the response comes in a corresponding Answer command. Do not send an OK when you receive a query. Send only an ANSWER. Otherwise your answer might get conflicted and be lost. When an answer is received by the querier, he, in turn, will need to acknowledge it with an OK. If the querier does not like the answer he got, he should send back a DENYinstead of an OK, which indicates the answer is unacceptable. The only query with a fixed meaning is the "What game are you" query. The meaning an interpretation of all other queries depends upon the game selected. Toyogo is not a clearinghouse for the meaning of queries in games other than Go. Bit s is on if the question is "Do you support extended command q?" Answer is: 15 the answer is Yes 0 the answer is No. Bit s is off if the question is one of the following: 0 = What game are you playing? Answer 0 = unknown 1 = Go 2 = Chess 3 = Othello . If your program only supports one kind of game, this question is unimportant to it. 1 = How big is your modem buffer ? Answer is multiplier of 16 bytes above the minimal 4 byte buffer. E.g., 0 = 4 byte minumum 1= 20 bytes 2= 36 bytes 2 = What version of the protocol do you understand? Answer is 0, the initial version. 3 = How many stones on your board? Answer is 0 is if you have no answer, or else its the number of stones on the board after you finish executing any command awaiting OK. Don't ask before the first voluntary move has been played; otherwise the answer is not well defined (he may not have put handicap stones on the board yet). 4 = How much time has Black spent ? Answer is 0 if you have no answer, or the number of minutes (rounded) spent by Black. There is no requirement that the Queryer use the answer. If he does, the recommended use is to set Black's clock to the minimum of the answer and the Queryer's own value. Time spent is the question, not time used, for two reasons. First, the value field is not interpreted as a signed number and Black could have spent his entire time allotment (be in byo-yomi or worse). Second, programs do not necessarily support the same time limits as interface choices. But if they support time, they know how much has been spent. Range is up to 1023 minutes (about 17 hours), error between programs is no more than 1 minute. 5 = How much time has White spent? Same concept as 4 above. 6 = What character set do you use? Answer: 0 = unknown 1= English (ascii) 2= Japanese. Other codes assigned on request. If the querier can match the language he should do so. If he cant, text defaults to the character sets of each machine (best of luck). If the language is a multi-byte character language (e.g., Japanese), text sent outside of the packets is in English. Text sent using the extended String command is multi-byte oriented (format currently unspecified) and represents that language. Accidental dropping of 1 byte of a multibyte string destroys the meaning of all other bytes, hence passing multi-byte languages via the checksummed packet is required. The notion of character set is used in supporting talk between two programs echoed into a talk window. Supporting talk is optional. 7 = What rules are you using? Answer: 0 = unknown 1 = Japanese 2 = Ing Chinese (SST laws). 8 = What handicap are you set for? Answer: 0 = unknown 1 = even 2..n are handicaps. See discussion of handicaps under MOVE command. 9 = What is the board size? Answer: 0 = unknown. N > 0 is NxN board size. 10 = What is the time limit per player Answer: 0 = unknown. N>0 is time in minutes. 11 = What color is the computer playing on your side. Answer: 0 = unknown. 1 = White 2 = Black 12 = who are you ? Answer is:. 0- unknown 1- NEMESIS 2- MANY FACES OF GO 3- SMART GO BOARD 4-GOLIATH 5- GO INTELLECT 6- STAR OF POLAND. Other ids will be assigned in future on demand. Contact Toyogo. If a program needs to identify its version number, a new Version # query will be created. SETUP AGREEMENT: The game setup parameters can be established between the two programs by query/answer. Whoever gets the first query command through can thereafter read off all the settings of the other program and either verify that they match, set himself up accordingly, or issue a DENY. If the querier gets back a 0 response, either he doesn't care, or he stops proceeding into the game. How the initial query command occurs and by whom is not specified (since it is not required to happen), but subject to program implementation. Be reminded that this feature may not even be supported by a program. In that case it is the responsibility of the two humans to see that the programs are set up correctly. 4. Answer command 1100 0aaa 1aaa aaaa THE ONLY REQUIRED ANSWER IS 0, if you haven't implemented any other answers. Sent back in response to QUERY, instead of OK. See Query for interpretation of a. A program is not required to QUERY, nor to do anything with the answer. If you receive an ANSWER, you must acknowledge it with an OK. 5. Move command 1101 0pii 1iii iiii Interpretation of the move command is game specific. For the game of Go, top value bit (p) is 1 if player to move is white or 0 if black. 9 bit value (i) is a board intersection value or 0 for pass. Intersection values run 1 at A1, 2 at B1 ... with value wrapping at end of row (varies with board size) so next row is A2. Range for 19x19 board is thus 1...361 (A1 ...T19). A1 is in the lower left corner and the letter I is omitted. A program is required to remember all the moves from the root position to the current position. It may, but is not required to, remember moves played later than the current position which have been taken back. A move always extends the current record of moves as the next move in sequence. A dumb program keeps no history of moves taken back, so extending the current move sequence is a simple matter. Complex programs may track variations or continuations, and may need to decide whether such a sequence is a replacement of the retracted moves, or a variant or what. Such decisions are the domain of the individual program. The program must only insure that the board stays consistent between the two programs. Also, while one may assume a program will not transmit an illegal move, the receiving program may check for that and issue a DENY instead of an OK if such a move is detected. Moves transmitted are moves that players (human or computer) have a choice about. See handicaps. Handicap moves: Handicap stones are a particularly thorny problem. The underlying premise of the protocol is that a pair of computers is shared cooperatively. Either player may place any color stones at any time, so there is no inherent program knowledge of who is Black and who is White. Therefore it is not clear who should send handicap moves to whom. Both programs might send the other messages about handicap stones, either in lump, in conflict, or intermixed with each other. Also, programs may represent handicap moves as either a contiguous sequence of Black moves, or a sequence of a Black move and White passes. Also handicap stones may be represented internally from other moves. The receiving program cannot tell what kind of move a stone is unless it knows the handicap in advance. So, with that can of worms in mind: Japanese Handicap Rules: Under Japanese rules, programs DO NOT transmit handicap setup stones. The first move transmitted will be White's first move. The placement of handicap stones for handicaps greater than 1 under Japanese rules on a 19x19 board use these intersections in order: D4 Q16 D16 Q4 (K10 when handicap is odd) D10 Q10 K4 K16. E.g., 2 stones at D4 & Q16, 5 stones at D4, Q16, D16, Q4, K10, 6 stones at D4, Q16, D16, Q4, D10, Q10. On odd board sizes of 13x13 and up, the corner handicaps are always on the 4th line and the center . On the 9x9 and 11x11, handicaps are on the 3rd line and the center. Order of placement always mimics the 19x19 case. Handicaps on even board sizes, sizes under 9x9, or handicaps beyond 9 stones are not predetermined by the standard. Reminder, if you transmit the moves of a game record, don't transmit the handicap stones under Japanese rules . Chinese Handicap Rules: The handicap passes by White are not transmitted. Transmitting game records: How a handicap game record under Japanese rules can be transmitted depends upon the sophistication of the receiving program. To transmit a game record, first transmit the NEWGAME command. Then, if the receiving program is sophisticated, it can query for the handicap and set itself up appropriately. The handcap under Japanese rules is not transmitted. If the receiving program is dumb, it will not know about the handicap. So you determine if he has asked you, and if not, you should ask him about his handicap prior to sending your recorded moves. If his handicap is set up correctly, send the moves w/o the handicaps. If his handicap is anything but correct, decline to send. If you want, if his handicap is even, you could send him all moves including the handicap, but the standard does not require it. If you want to take the lazy approach, always transmit the game as an even game. 6. Take back move command 1110 0ttt 1ttt tttt The meaning of taking back a move is game specific. In Go, it is taking back a move a player had a choice about. Under Japanese rules Black's handicap moves are fixed and cannot be taken back. Under Chinese rules White's passes are required. Hence, taking back a move means removing a Black handicap stone, and any White passes needed to accomplish this. Value t is how many moves to retract, and ranges from 0 to 1023. Taking back 0 has no effect. A turn may consist of multiple moves (e.g., the SETUP command in "Standard Format" for text records. The meaning of take back is retracting a move, not a turn. If a program is unable to obey the Take back move command given it, it may respond with a DENY instead of an OK. 7. Extended command 1111 0mmm 1mmm mmmm 1nnn nnnn 1sss ssss SUPPORT OF EXTENDED COMMANDS IS OPTIONAL. Do not send such a command unless you have queried and discovered that recipient supports it. Also be aware that receiver's buffer size may not fit everything you want to send and you might have to break it into smaller chunks. You should determine his buffer size with a QUERY command. The value of the extended command is how many MORE bytes follow after the basic four. At a minimum, the value is two. The first following byte has the extended command name (n). The second following byte is a checksum (s) computed for the extended part of the message (bytes 5, 7...4+m). Programmers wishing an assigned name can contact Toyogo. Commands created can be optionally supported by more than one program. Programs are not required to support extended commands. They may discard such a message if they wish, but if queried about such support they must be able to answer NO. Current extended command names are: 0 String 1000 0000 The contents are to be displayed as talk output. They are analogous to text sent outside of packets except 1) they are secure (checksummed) and 2) they can represent multi-byte languages if needed. 1. Replay 1000 0001 1nnn nnnn 1nnn nnnn If a program keeps track of moves taken back, then this command is the inverse of TAKE BACK. The third and fourth extended bytes (n) are the count of how many moves to replay. 15 Multiple 1000 1111 The contents are multiple commands (without individual header and checksum bytes) intended to be executed as one. The purpose is to speed up response to complex sequences of actions, and/or to insure control is retained by sender for the duration of the sequence. OK, DENY, QUERY, ANSWER, and the multiple extended command MAY NOT be used within a multiple command. CONFLICT If both players send commands at each other at the same time, this is detected as a conflict. Conflict is detected when you receive a new command from him while you are awaiting his OK, and the new command does not have your current sequence number. Programs detecting conflict are required to retract their command (decrement their sequence id, stop waiting for an OK on their command, and undo any effects their command had on them. E.g., if they placed the move of the MOVE command, they should unplace it. It is not essential that both programs detect the conflict, though they will if there are no tranmission problems. A program which is in the middle of a sequence of commands it wishes to send, may decide to retry transmitting its conflicted command after a random delay (so the two programs don't stay in conflict). If it receives an intervening foe command before it has completed its own list, it may issue a DENY if accepting his command would be a problem. Issuing a DENY prohibits him from continuing his attempt to send you his command. If he sends you a DENY in the middle of your sequence, you must stop sending further commands from that sequence. DONT DENY A DENY. AUTOMATIC CHECKING & TIME SYNCHRONIZATION: QUERY can be used on a regular basis to check that the stone count matches and keep the player times in synch. This can be done automatically by the program, but such commands should be kept from interfering (CONFLICT) with user commands without good cause. To avoid conflict with foe user, send an automated QUERY whenever you would send an OK. You are not safe from your own user unless you buffer his command for execution for when the query/answer/ok is complete or otherwise prevent him from executing a command until then. TALK TEXT outside of packets are sent with top bit off (7bit ascii notation). CTRL G must make sound or flash or equivalent when printed into talk area. CTRL-H (Ascii 8, backspace) should have the correct effect on both machines. Text is intended to be echoed into the talk window of the other machine. Usually you will also echo the talk into your own window as it is typed. Supporting talk windows is not required, and a program may just discard incoming text if it wishes. INITIAL CONDITIONS The protocol requirements for using the format are as follows: 1. Sequence IDs for you and he both start at 0. 2. Your sequence ID is incremented before creating a new non-OK message, and is incorporated into the message. THE FIRST MESSAGE EVER SENT WILL HAVE A START BYTE OF 0100 0001, which says foe's last message was 0, and my new sequence value is 1. 3. Any program can spontaneously send the first command. You should assume a program cannot accept any extended commands and answers 0 to any query until you query successfully otherwise. 4. His sequence ID is taken from the incoming command after it is successfully validated by you. 5. When you transmit a non-OK command, you are prohibited from transmitting any NEW commands until you receive or infer an OK from an incoming message of his. 6. After you transmit a non-OK command, if you receive no acknowledgement from him within some time period, you should retransmit the same message. Retransmission changes no sequence data. PARSING: Incoming characters fall into 3 classes: text, start byte, other command bytes. Text is any non-start byte having the top bit off. If you detect other command bytes and have no corresponding start byte, just discard the extraneous command bytes. STATE ACTION CHART: You have just received a message. Your current sequence id is B of {A,B}. His last sequence ID you saw was 2 of {1,2}. What should you do and what does it mean? Neutral: (sitting idle with no transmission, you have no unfinished commands) OK(any) OK(B2) indicates repeated OK. Others impossible. Discard all. CMD(A1,A2) New or Old command not possible to receive. Discard. CMD(B1) Normal New command. Do it + send back OK(before or after). CMD(B2) Old Command . He hasnt seen my OK yet. Resend last comand (implicit OK or real OK). OKWait: (retransmiting your command every n seconds until OK received or implied) OK(A1,A2,B1) Not possible. Discard. OK(B2) Normal OK. Terminate OK Wait (Go to neutral state). CMD(A1) New Command from him, he hasn't seen my command yet. This is conflict. Do not respond with an OK! Undo my command, revert my sequence id, don't update his id, and go to neutral. If he sees my command, he will do likewise. If he doesn't see my command he will rebroadcast his and win the toss. CMD(A2) Old Command, he hasn't seen my new command. He was unaware of my OK at that time. Discard. Retransmit your command ahead of timeout rebroadcast if you want to. CMD(B1) New Command from him, but he has seen my command. He must have sent OK and I missed it or has some reason to insure we don't conflict. Go to neutral state and then do command+ send back OK (before or after). CMD(B2) Not possible. Discard. TIMING: The protocol allows a program to make its own choice about when to send a command or an OK. The most simple & inefficient way is to send the command, wait for his OKand then execute your command. If he sends his OK after he has completed execution, that means delaying the time of two machines implementing the command. However, if you do not wait for his OK before executing your command, you must be prepared to undo the command if conflict arises. If he can send the OK upon successful receipt of your command, both of you can process the command in parallel. Programs may also choose their own rebroadcast frequency. Several seconds should be allowed for the receiving program to execute and acknowledge the command, but there is no requirement, since thereceiver can just discard any messages it wishes. If both programs generate an automatic QUERY or NEWGAME on startup the two programs may get a CONFLICT. If the programs back off and try again later, they may still conflict. The protocol does not prevent an infinite loop in such a situation. Our advice is that if you CONFLICT and you are using a loop to try again, you should select a random delay within an ever increasing delay range. You may need to delay longer than the other program's rebroadcast delay to succeed. Alternatively after n conflicts without an accepted command on either side, you might stop and report to the user. He should be able to reinitiate the loop at his discretion. We recommend that the first command sent be a NEWGAME command by whoever is Black. The receiver can query to establish game parameters, and then Black can send his first move. This is the code used by Nemesis to implement this protocol, written for Lightspeed C on the Mac. #include#define MaxBuffer (96 + 5) /* 1 byte for length code, the rest for buffer */ /* the code allows room to listen to some extended commands, but doesnt use them */ #define Obuffer 5 /* we only write 4 byte messages (+ length) */ INT pending = 0; /* characters expected to receive to finish packet*/ #define CTRL_G 0x07 /* talk recognizes CTRL_G as sound */ /* 8 Commands are in COMMANDBITS */ #define COMMANDBITS 0x70 /* mask holding all commands */ #define OKMSG 0x00 #define DENY 0x10 #define RESET 0x20 #define QUERY 0x30 #define RESPONSE 0x40 #define MOVE 0x50 #define TAKEBACK 0x60 #define EXTENDED 0x70 #define STRING 0x00 #define REPLAY 0x01 /* QUERY types are */ #define FEATUREBIT 0x0400 /* asks if you support extended command named */ #define GAMEID 0 /* what game 1 = go */ #define HOWBIG 1 /* your buffer size in 16byte units above 4 byte minimum */ #define PROTOCOLID 2 /* Currently Protocol version is 0 */ #define BOARDVERIFY 3 /* number of stones on board after current command finished.*/ #define BLACKTIMEUSED 4 /* seconds spent by black */ #define WHITETIMEUSED 5 /* seconds spent by white */ #define LANGUAGE 6 /* what character set */ #define WHATRULES 7 /* what rules */ #define WHATHANDICAP 8 #define WHATSIZE 9 #define WHATTIME 10 #define WHATCOLOR 11 #define WHOAREYOU 12 /* what program. I am NEMESIS */ /* answers are */ #define NEMESIS 1 #define WHITEMOVE 0x0200 /* bit on move to indicate White color */ static unsigned char nconflicts = 0; /* conflicts in progress */ #define RETRYDELAY (4 SECONDS) /* how long I wait for OK before rebroadcasting */ #define QUERYDELAY (120 SECONDS) /* how often I poll to keep time consistent */ unsigned char outmbuffer[Obuffer],inmbuffer[MaxBuffer]; long timesent = 0; /* to tell when to rebroadcast */ static char oursequence = 0; /* our last sequencing bit */ static char hissequence = 0; /* his last sequencing bit */ static long timequery = 0; /* to tell when to reverify */ static int lastquery = -1; /* If i have a query awaiting an answer */ static unsigned int querycnt = -1; /* for cycling thru my queries */ static unsigned int movecnt = -1; /* to tell when to rebroadcast board verify */ INT modemmove = -1; /* echo expected for this location, dont send it out */ /* HOST SPECIFIC MODEM ROUTINES */ VOIDFN OutModem (echo) INTM echo; {/* transfer serial output*/ long count = (long)outmbuffer[0]; unsigned int pt,val; /* send new message */ outuser(M_FIXTIME,0,0); /* update time */ timesent = time_count; /* when we sent the message*/ if (FSWrite(ModemOut, &count, outmbuffer+1) != noErr) return -1;} int InModem () {/* transfer serial input into input buffer*/ /* CALLED REPEATEDLY FROM EVENT LOOP */ long one = 1,cnt; int type,val; unsigned char c; SerGetBuf(ModemIn, &cnt); /* bytes available to be read */ while (cnt-- > 0) { /* read all available characters in buffer */ if (FSRead(ModemIn, &one, &c) != noErr) {/* failed to read */ return -1;} if (c > 3 && c < 128) {/* talk character */ if (pending) pending = inmbuffer[0] = 0; rawabsorb(c); continue;} /* start of packet recognized will need 4 bytes */ if (c < 4) { pending = 4; inmbuffer[0] = 0;} /* Have a command byte, discard if not expected, absorb if expected*/ if (pending){ inmbuffer[++inmbuffer[0]] = c; /* move it to buffer */ --pending; if (inmbuffer[0] == 4) { /* have 1st 4 bytes complete */ if (!checksum()) { /* BAD CHECKSUM */ inmbuffer[0] = 0; continue;} type = gett(inmbuffer); /* lets see if it is an extended command */ val = getv(inmbuffer); /* and get size */ if (type == EXTENDED) { if ((val + 5) <= MaxBuffer) pending = val;/* accept read*/ else { senddeny("extended too big"); inmbuffer[0] = 0;}}}}} return dominput();}/* see what you have */ /* GENERIC MODEM ROUTINES */ INTFN resetmodem( ) {/* clear the modem interface */ modemwaiting = inmbuffer[0] = oursequence = hissequence = 0;} static VOID mylisten(text) char *text; { listen(text);} /* this sends text to my talk window */ static VOID myoutmodem(n) INTM n; { OutModem(n);} VOIDFN SendTalk(c) char c; {/* our keyboard letter we sent to us and him */ char msg[2]; msg[0] = c; msg[1] = 0; mylisten(msg); /* tell my talk */ outmbuffer[0] = 1; outmbuffer[1] = c; myoutmodem(FALSE);} /* tell out modem */ static void xmit(OK) INTM OK; {/* add header into message */ unsigned int sum; outmbuffer[0] = 4; /* message is this long */ if (!OK) oursequence ^= 1; /* flip our sequence */ outmbuffer[3] |= 0x80; /* required bit */ outmbuffer[4] |= 0x80; /* required bit */ sum = outmbuffer[1] = (hissequence << 1) | oursequence; sum += outmbuffer[3] + outmbuffer[4]; outmbuffer[2] = sum | 0x80; /* put out checksum */ myoutmodem(TRUE);} /* send the completed message */ static void sendcmd(cmd,val) INTM cmd,val; {/* send out command */ if (cmd == QUERY) lastquery = val; outmbuffer[4] = val & 0x7f; outmbuffer[3] = (val >> 7) & 0x03; outmbuffer[3] |= cmd; xmit(0); modemwaiting = 2;} VOIDFN senddeny(msg) char *msg; { /* msg just tells us why we are denying him */ sendcmd(DENY,0);} static void sendok(type) INTM type; {/* ok his last message */ nconflicts = 0; /* automatic polling, unless he is querying us */ if (type == QUERY); /* requires RESPOND and not ack so dont */ else if ((movecnt % 10) == 9) { /* auto poll for stone count periodically */ sendcmd(QUERY,BOARDVERIFY);/* doublecheck the board */ ++movecnt;} else if (G(NOWPLAYING) == S_PLAYING && (time_count timequery) > QUERYDELAY) {/* low level status first */ sendcmd(QUERY,(querycnt & 1) ? BLACKTIMEUSED : WHITETIMEUSED); timequery = time_count;} /* dont query again for a while */ else { outmbuffer[3] = 0x07; /* ack command, reserved bit, and message */ outmbuffer[4] = 0xff; /* ack message */ xmit(TRUE);}}/* send OK */ VOIDFN SendMove ( pt,multi) INTM pt,multi; {/* move encoded color etc */ register INT loc = LOCATION(pt), color = WHOPLAYED(pt),where; char junk[30]; where = loc; /* if move is during handicap setup, dont echo forced moves */ /* tested before move is placed on board */ if (G(HANDICAP) > 1 && gethandicap() < G(HANDICAP)) return TRUE; /* not enough black stones yet*/ if (where) where = X(where) + ((Y(where) 1) * G(BDSIZE)); /* grid ref */ ++movecnt; /* autopoll of stone count*/ sendcmd(MOVE + ((color == WHITE) ? 0x04 : 0),where);} VOIDFN SendNewGame( ) { /* clear */ sendcmd(RESET,0);} VOIDFN SendUnmove( n) INTM n; {/* retract this many moves */ ++movecnt; /* autopoll stone count */ sendcmd(TAKEBACK,n);} /* only done by a user anyway */ VOIDFN rawabsorb(c) unsigned char c; { /* echo what he ships immediately*/ char msg[2]; if (c == CTRL_G) outuser(DOSOUND,OTHERBEEP,0); /* sound beep */ else { msg[0] = c; msg[1] = 0; mylisten(msg);}} /* says his message */ INTFN checksum() { unsigned char c; c = ((inmbuffer[1] + inmbuffer[3] + inmbuffer[4]) | 0X0080) & 0x00ff; return (c == inmbuffer[2]);} INTFN gett(buf) unsigned char *buf; { return buf[3] & COMMANDBITS;} /* 3 bit type */ INTFN getv(buf) unsigned char *buf; { return (buf[4] & 0x7f) | ((buf[3] & 0x07) << 7);} /* 10 bit value field */ static void undolast(deny) INTM deny; {/* take back last command */ register INT type = gett(outmbuffer), val = getv(outmbuffer); INT tmp; lastquery = -1; /* there is no query pending now */ if (type == MOVE) { if (deny) setflag(NOWPLAYING,S_STOPPED); /* stop trying */ unmove();} else if (type == TAKEBACK) { tmp = NTURN + val; while (NTURN < tmp) doforward(0);} /* nothing to do for QUERY, RESPOND, EXTENDED */ } static INT bdcount() { register INT i,n = 0; SWEEP361 if (ISSTONE(i)) ++n; return n;} static INT inpmove(val) INTM val; { register INT tmp,pt,ans; tmp = (val & WHITEMOVE) ? WHITE : BLACK; /* who is to play */ pt = LOCATION(val) 1; pt = MAKECOORD( (pt % G(BDSIZE)) + 1, (pt / G(BDSIZE)) + 1); if (!legalcheck(pt,tmp)) { /* illegal move */ senddeny("illegal move"); /* send a denial */ return 0;} if (tmp != nextplayer()) G(FLIPTURN) = NTURN; /* switch mover */ cmdval = pt; cmd2val = 0; modemmove = pt; /* we got this by modem clear it when it echos*/ ++movecnt; ans = ClickedAtPoint(pt,0,0,0); /* see if incoming move has legal context (his move) */ if (!ans) senddeny("not his turn"); /* send a denial */ return ans;} static int nemabsorb() { /* eat nemesis data */ INT i,j,ans = 0,pt,val = getv(inmbuffer),type = gett(inmbuffer),him,us,tmp,oldquery = lastquery; unsigned int sum; long time; us = (inmbuffer[1] & 0x02) >> 1; /* his last seen message of ours */ him = inmbuffer[1] & 0x01; /* his current message id */ /* VALIDATE OK COMMAND */ if (type == OKMSG) { /* OK command */ if (us != oursequence || him != hissequence); else if (!modemwaiting); else nconflicts = modemwaiting = 0; /* normal OK */ goto exit;} /* validate command */ /* STATE: NEUTRAL */ if (!modemwaiting) {/* we are in neutral */ /* CASE 2: he is repeating an old command */ if (him == hissequence) { /* CASE B: he knows our last command but has missed our OK */ if (us == oursequence) myoutmodem(TRUE); /* repeat OK as an echo*/ /* he didnt see our OK, help him */ /* CASE A: he doesnt know our last command since it cleared, not possible */ ; goto exit;} /* CASE 1: he is sending a new command */ /* CASE A: he doesnt know our last command, but it cleared not possible */ else if (us != oursequence) {/* his new command does not recognize our old command*/ goto exit;} /* CASE B drops thru: he has new message and he saw our last command so he is ok.*/} /* STATE: OK_WAIT */ /* CASE B: he knows our last command */ else if (us == oursequence) { /* we have a command pending, he has seen our command */ /* CASE 2: he sends old command can't do that and know of our command */ if (him == hissequence) {/* he repeats his old command */ goto exit;} /* not possible */ /* CASE 1: he sends new command knowing of our old one implicit OK */ else modemwaiting = FALSE;} /* we must have missed his OK */ /* CASE A: he does not know of our command yet */ else { /* he has not seen our command */ /* CASE 2: he sends old command he missed OK */ if (him == hissequence) myoutmodem(TRUE);/* repeated old cmd, our resend will fix him*/ /* CASE 1: he sends new command */ else { /* His Command in Conflict with ours */ ++nconflicts; oursequence ^= 1; /* revert to before we shipped command */ /* leave his sequence alone-pretend we didnt hear his command */ /* he will hear us and quit, or echo and win. */ modemwaiting = FALSE; undolast(nconflicts > 4);}/* UNDO COMMAND */ goto exit;} /* ignore his command */ hissequence = him; /* we recognize his command */ /* screen his command for legality with us */ if (type == MOVE) { /* verify a move loc or pas loc */ ans = inpmove(val); /* setup move or decline */ if (!ans) goto exit;} else if (type == TAKEBACK) { if (NTURN < val) { senddeny("takeback too big"); /* deny */ goto exit;} modemmove = 1000;} /* came by modem */ else if (type == DENY) { undolast(TRUE);} else if (type == EXTENDED) { senddeny("Extended not accepted"); goto exit;} /* send an acknowledge *//* the command is self-consistent */ sendok(type); /* release him (except QUERY)... we expect to do his command */ if (type == TAKEBACK) { ++movecnt; tmp = NTURN val; /* go back to this turn */ while (NTURN > tmp && NTURN > 0) dobackup(TRUE);} else if (type == QUERY) {/* give him an answer */ tmp = 0; /* If his query freed us from OK-wait, this will put us back again in it */ if (val & FEATUREBIT) tmp = 0; /* we dont do windows etc */ else if (val == GAMEID) tmp = 1; /* go */ else if (val == HOWBIG) tmp = (MaxBuffer 5) / 16; else if (val == PROTOCOLID) tmp = NEMESIS; else if (val == BOARDVERIFY) tmp = bdcount(); else if (val == BLACKTIMEUSED) tmp = (INT) (bticks/60) ; else if (val == WHITETIMEUSED) tmp = (INT) (wticks/60); else if (val == LANGUAGE) tmp = 1; /* english ascii*/ else if (val == WHATRULES) tmp = G(CHINESERULES) + 1; else if (val == WHATHANDICAP) { tmp = G(HANDICAP); if (tmp < 2) tmp = 1;} else if (val == WHATTIME) tmp = G(TIMELIMIT); else if (val == WHATSIZE) tmp = G(BDSIZE); else if (val == WHATCOLOR) { /* what computer color */ if (bcomputer() && !wcomputer()) tmp = 2; else if (wcomputer() && !bcomputer()) tmp = 1;} else if (val == WHOAREYOU) tmp = NEMESIS; /* react negatively to questions */ sendcmd(RESPONSE,tmp);} else if (type == RESPONSE) {/* answer to my question */ if (oldquery == BOARDVERIFY) { sprintf(myouttext,"\r ",val,bdcount()); if (val != 0 && val != bdcount()) mylisten(myouttext);} else if (oldquery == BLACKTIMEUSED && val != 0) {/* settle on min time */ time = val * 60; if (bticks > time) bticks = time;} else if (oldquery == WHITETIMEUSED && val != 0) {/* settle on min time */ time = val * 60; if (wticks > time) wticks = time;} lastquery = -1;} /* I will not recognize future response w/o query */ else if (type == RESET) {/* refresh */ outuser(M_NEWGAME,0,0); modemwaiting = 0;} /* eat off message accepted */ exit: inmbuffer[0] = 0; /* we only allow 1 message in buffer */ return ans;} INTFN dominput() { if (inmbuffer[0] == 0 || pending) {/* no message completed yet */ if (modemwaiting) {/* we are expected a message */ if ((time_count timesent) >= RETRYDELAY) myoutmodem(2);}} /* repeat the message as an echo*/ else if (G(MODEM_STATE) == 2 && !inside) return nemabsorb(); /* we have a command to eat */ return 0;} This is the code used by Many Faces of Go to implement this protocol, written in Microsoft C 6.0 for the IBM-PC. It includes a serial COM port interrupt handler. # include # include "g2hd.h" # include "keys.h" /* Copyright 1991 david fotland. Permission granted to use this * code for any commercial or noncommercial purposes as long as this * copyright notice is not removed. This code was written for * Microsoft C 6.0 */ #ifdef __STDC__ static void putcommand(char *com); void putamove(int ptr); int domodem(); static void fillsendpacket(); static void sendthepacket(); void displaychar(char c, int side); static void domodemcommand(); static void takeback(); void puttakeback(int n); static void putquery(int query); #else static void putcommand(); void putamove(); int domodem(); static void fillsendpacket(); static void sendthepacket(); void displaychar(); static void domodemcommand(); static void takeback(); void puttakeback(); static void putquery(); #endif /* modem interface routines. putmodem() puts a single character out through * the modem. getmodem() gets a single character from the modem or returns * FALSE if one is not available. The host interface is through * putmodem() for typed characters for the talk window, and * putamove(), puttakeback(), and putreset() for commands. * The host should call domodem() frequently. * displaychar() is called to display input characters in the talk window. * domodemcommand() is called to parse the modem command string. * * Modem format is: * * Bytes outside of packet 0CCCCCCC. * C is character to echo to the talk window. All characters typed during * a game are echoed in the local talk window and sent over the modem. * The ^G character should ring the bell, not be echoed into the window. * * Byte 1 000000AS (Start packet) * A is Ack bit same as seq from previous received command * S is Seq bit toggle for each sent command except OK * Byte 2 1KKKKKKK * K is checksum sum of bytes 1,3,4. Received packets with checksum * errors are ignored. * Byte 3,4 1CCCRVVV 1VVVVVVV * C is command, R is reserved, V is value. * Commands: * 0 OK. Value is 0. Send in response to received command other than * OK. Used to detect simultaneous actions and conflicts. Do * not accept new move or retract from user until see OK for previous * command. If no OK for two seconds, send command again (with same * Seq). * 1 Denial. Deny receipt of last command. * 2 Reset. Clear board and start new game does not reset sequence * numbers * 3 Query. Value is: * 1EEEEEEE Do you support extended command E? // Correct value is 1EEEEEEEEE according to Joe Author * Respond with 0x0f: Yes, 0: No * Others described in code below. * 4 Response. Value as described above. Sent in response to a query * command. * 5 Move. MSB of value is 0: Black, 1: White. Rest of value is square * number. 0: pass, 1: lower left corner, boardsize: lower right corner. * 6 Take back. Value is number of moves to take back (0-1023) * 7 Extended command. Value is number of additional bytes (1-1024) * * Additional byte 1 1EEEEEEE * E is extended command * Additional byte 2 1KKKKKKK * K is checksum: Sum of all additional bytes except this one. Received * packets with checksum errors are ignored. */ # define WHITEUNDO 1024 # define TAKEBACK 2048 # define PROTOCOL_VERSION 0 # define PROGRAM_ID 2 # define EXTRABUFSIZE 16 # define STARTMASK 0xfc # define STARTVAL 0 /* commands */ # define HLACMD 0 # define DENIALCMD 1 # define RESETCMD 2 # define QUERYCMD 3 # define RESPONDCMD 4 # define MOVECMD 5 # define TAKEBACKCMD 6 # define EXTENDEDCMD 7 # define STRINGCMD 0 # define MULTICMD 15 /* queries */ # define QUERYGAME 0 # define QUERYBUF 1 # define QUERYPROTOCOL 2 # define QUERYSTONES 3 # define QUERYBTIME 4 # define QUERYWTIME 5 # define QUERYCHARSET 6 # define QUERYRULES 7 # define QUERYHANDICAP 8 # define QUERYBOARDSIZE 9 # define QUERYTIMELIMIT 10 # define QUERYCOLOR 11 # define QUERYWHO 12 # define QUERYSTRING 0x400 // The correct value is 0x200, according to Joe Author # define QUERYMULTI 0x40f /* responses to QUERYWHO */ # define NEMESIS 1 # define MFGO 2 # define SMARTGO 3 # define GOLIATH 4 # define GOINT 5 # define STARPOL 6 extern int modemconnected; /* modem connection exists */ extern char colordisplayed[]; /* color displayed on board per point */ extern list_t highlighted; /* points that are highlighted */ extern int compmoveinprogress; /* computer is thinking */ list_t undocommands = EOL; /* commands for undo */ unsigned char hostdata[4]; /* command ready to send */ unsigned char sentdata[4]; /* command most recently sent */ unsigned char recdata[4]; /* data most recently received */ char recextra[EXTRABUFSIZE]; /* buffer for extra received data */ char sendextra[EXTRABUFSIZE]; /* buffer to accumulate output multiple command */ int hisbuffersize = 0; /* his extra buffer size */ int hesupportsmulti = FALSE; int hesupportsstring = FALSE; int whoishe = 0; int hishandicap = 0; int hisboardsize = 0; int hisrules = 0; int querylist[] = { QUERYWHO, QUERYRULES, QUERYHANDICAP, QUERYBOARDSIZE, -1}; /* queries to send at startup */ int nextquery = 0; /* next query in querylist to send */ int lastquerysent = 0; /* which query does response match? */ int mylastseq = 0; /* my last sequence number */ int hislastseq = 0; /* his last sequence number */ int hisprotocolversion = PROTOCOL_VERSION; int hisprogramid = PROGRAM_ID; /* assume talking to myself */ char waitinghighack = FALSE; /* waiting for acknowledgement */ int querysent; /* which query did I send */ int denycount = 0; /* how many consecutive denials */ long sendtime,firstsendtime; /* when did I send last */ int randtime = 0; /* random timeout to resend after conflict or deny */ /* set up to query again when start new game */ newquery(){ if(querylist[nextquery] == -1)nextquery = 1; } /* calculate the checksum of packet p */ static unsigned char checksum(p) unsigned char p[4];{ unsigned char sum; int i; sum = p[0] + p[2] + p[3]; sum |= 0x80; /* set sign bit */ return(sum); } /* put the two byte command in com into hostdata and make it ready to send */ static void putcommand(com) char com[2]; { hostdata[0] = 0x0; /* make first byte */ hostdata[2] = com[0] | 0x80; hostdata[3] = com[1] | 0x80; fillsendpacket(); mylastseq = sentdata[0] & 1; sendthepacket(); sendtime = time10(); firstsendtime = sendtime; } /* send him an OK */ static void putack(){ unsigned char sendc[2]; if(querylist[nextquery] != -1){ /* can send next query instead of HLA */ putquery(querylist[nextquery]); return; } sendc[0] = (HLACMD << 4) | 7; sendc[1] = 0xff; putcommand(sendc); } /* deny his command force him to undo it send take back 0. */ static void putdenial(){ unsigned char sendc[2]; waitinghighack = TRUE; sendc[0] = DENIALCMD << 4; sendc[1] = 0x0; putcommand(sendc); } /* send a new game command */ void putreset(){ unsigned char sendc[2]; waitinghighack = TRUE; sendc[0] = RESETCMD << 4; sendc[1] = 0x0; putcommand(sendc); } /* send a move over the modem. mvs[ptr] contains the location of * the move (0-360, or PASS). mvcolor[ptr] is the color of the move * (0-black, 1-white). */ void putamove(ptr) int ptr; { unsigned char sendc[2]; int val,x,y; if(!modemconnected)return; if(waitinghighack){ outerr("Internal error not ready for command!\n"); return; } waitinghighack = TRUE; sendc[0] = MOVECMD << 4; if(mvcolor[ptr] == WHITECOLOR)sendc[0] |= 1 << 2; if(mvs[ptr] == PASS) val = 0; else { x = xval[mvs[ptr]]; y = yval[mvs[ptr]]; val = x+1+boardsize*(boardsize-y-1); } sendc[1] = val; sendc[0] |= val >> 7; outstatus("Sending"); adflist(TAKEBACK,&undocommands); /* put take back on undo list */ putcommand(sendc); } /* take back n moves. must be called while moves are still in mvs[] */ void puttakeback(n) int n; { unsigned char sendc[2]; int i; if(!modemconnected)return; if(waitinghighack){ outerr("Internal error not ready for command!\n"); return; } waitinghighack = TRUE; sendc[0] = TAKEBACKCMD << 4; sendc[0] |= n >> 7; sendc[1] = n & 0x7f; outstatus("Sending"); for(i = msptr; i > msptr-n && i >= 0; --i) adflist((WHITEUNDO*(mvcolor[i] == WHITECOLOR))+mvs[i],&undocommands); putcommand(sendc); } /* query the other program. */ static void putquery(query) int query; { unsigned char sendc[2]; if(!modemconnected)return; if(waitinghighack){ outerr("Internal error not ready for command!\n"); return; } lastquerysent = query; waitinghighack = TRUE; sendc[0] = QUERYCMD << 4; sendc[0] |= (query >> 7) & 7; sendc[1] = query; putcommand(sendc); } /* respond to a query */ static void putresponse(){ int query,n,i; unsigned char sendc[2]; waitinghighack = TRUE; query = (recdata[3] & 0x7f) | (recdata[2] << 7); query &= 0x3ff; sendc[0] = RESPONDCMD << 4; sendc[1] = 0; switch(query){ case QUERYGAME: sendc[1] = 1; /* GO */ break; case QUERYWHO: sendc[1] = PROGRAM_ID; break; case QUERYBUF: sendc[1] = 4 + EXTRABUFSIZE/16; break; case QUERYPROTOCOL: sendc[1] = PROTOCOL_VERSION; break; case QUERYSTONES: n = 0; for(i = 0; i < boardsquare; ++i) if(colordisplayed[i] != NOCOLOR)++n; sendc[1] = n; break; case QUERYCHARSET: sendc[1] = 1; /* ascii */ break; case QUERYRULES: sendc[1] = chineseflag + 1; break; case QUERYHANDICAP: sendc[1] = handicap; if(sendc[1] = 0)sendc[1] = 1; break; case QUERYBOARDSIZE: sendc[1] = boardsize; break; case QUERYCOLOR: if(cplay[WHITECOLOR])sendc[1] = 1; else if(cplay[BLACKCOLOR])sendc[1] = 2; break; case QUERYSTRING: sendc[1] = 0; /* no string support yet */ break; case QUERYMULTI: sendc[1] = 0; /* no multisupport yet */ break; } putcommand(sendc); } /* domodem handles the modem interaction. It * reads an acknowledges any packet available from the * input port and executes the command. * it returns TRUE if a command was executed, or a move was taken back. */ int domodem(){ long t; unsigned char seq,ack,hla; int command,retval = FALSE; if(!modemconnected)return(FALSE); while(getpacket()){ /* handle input packet */ seq = recdata[0] & 1; ack = (recdata[0] & 2) >> 1; command = (recdata[2] >> 4) & 0x7; if(!waitinghighack){ if(command == HLACMD)continue; else if(ack != mylastseq)continue; else if(seq == hislastseq) putack(); /* he missed HLA, resend */ else { hislastseq = seq; domodemcommand(); retval = TRUE; } } else { /* waiting for OK */ if(command == HLACMD){ if(ack != mylastseq || seq != hislastseq) continue; /* sequence error */ waitinghighack = FALSE; denycount = 0; gamestatus(); /* tell user ready for command */ killist(&undocommands); } else if(seq == hislastseq)continue; else if(ack == mylastseq){ waitinghighack = FALSE; gamestatus(); /* tell user ready for command */ hislastseq = seq; domodemcommand(); killist(&undocommands); retval = TRUE; } else { randtime = rand()%400+200; /* back off for random time 2-6 seconds */ clearerror(); outerr("Conflict with opponent"); takeback(); /* conflict */ mylastseq = 1-mylastseq; retval = TRUE; waitinghighack = FALSE; continue; } } } t = time10(); /* timeout send packet again or give up */ if(waitinghighack && t-sendtime > 200){ /* 2 seconds */ if(t-firstsendtime > 6000){ /* 60 seconds */ outerr("Missing command acknowlege, continuing"); waitinghighack = FALSE; gamestatus(); killist(&undocommands); } else { sendtime = t; sendthepacket(); } } return(retval); } /* fill the send packet */ static void fillsendpacket(){ int i,command; char *dummy = NULL; for(i = 0; i < 4; ++i)sentdata[i] = hostdata[i]; command = (sentdata[2] >> 4) & 0x7; if(command == HLACMD) sentdata[0] |= mylastseq; /* set up sequence number */ else sentdata[0] |= 1-mylastseq; /* set up sequence number */ sentdata[0] |= hislastseq << 1; /* ack his last message */ sentdata[1] = checksum(sentdata); } /* sendthepacket sends a packet through the modem */ static void sendthepacket() { int i,command; char buf[100]; for(i = 0; i < 4; ++i) putmodem(sentdata[i]); #ifndef PCMIN if(debug != 0){ command = (sentdata[2] >> 4) & 0x7; sprintf(buf,"sent cmd %d, ack %d, seq %d\n",command,(sentdata[0]&2)>>1,sentdata[0] & 1); outerr(buf); } #endif } /* get packet assembles a packet from the modem input. It returns TRUE * if a packet has been assembled in receivepacket, and FALSE otherwise. * if it returns FALSE, there are no more characters in the modem input buffer. * it handles characters outside of packets. */ static char nextpacketbyte; static int nextextrabyte,numextrabytes; static char receivestate = 0; /* 0 waiting for start of packet * 1 reading packet * 2 reading extra data */ # define COUNTLIMIT 1000 int getpacket(){ unsigned char c; int command,count = 0,flag; char buf[100]; while((flag = getmodem(&c)) || receivestate && count++ < COUNTLIMIT){ if(!flag)continue; /* get a character from the modem if available */ /* if get first character of a packet, spin wait for rest */ switch(receivestate){ case 0: /* idle, looking for start of packet */ #ifndef PCMIN if(debug != 0 && (c&STARTMASK) != STARTVAL){ sprintf(buf,"%x\n",c); outerr(buf); } #endif if((c&STARTMASK) == STARTVAL){ /* start ofpacket */ receivestate = 1; recdata[0] = c; nextpacketbyte = 1; } else if(c&0x80)break; /* error */ else displaychar(c,1); /* character outside of packet, display it */ break; case 1: /* reading packet */ if((c&0x80) == 0){ /* error */ if((c & STARTMASK) == STARTVAL){ receivestate = 1; recdata[0] = c; nextpacketbyte = 1; break; } else { receivestate = 0; displaychar(c,1); } break; } recdata[nextpacketbyte++] = c; if(nextpacketbyte == 4){ /* check for extra bytes */ command = (recdata[2] >> 4) & 0x7; if(command == EXTENDEDCMD){ receivestate = 2; nextextrabyte = 0; numextrabytes = recdata[3] & 0x7f; numextrabytes |= ((int)recdata[2] & 7) << 7; } else { /* done, no extra bytes */ receivestate = 0; #ifndef PCMIN if(debug != 0){ command = (recdata[2] >> 4) & 7; sprintf(buf,"getpacket: cmd %d ack %d seq %d\n",command,(recdata[0] & 2)>>1, recdata[0] & 1); outerr(buf); if(checksum(recdata) != recdata[1]) outerr("checksum error"); } #endif return(checksum(recdata) == recdata[1]); /* good packet if checksum matches */ } } break; case 2: if((c&0x80) == 0){ /* error */ if((c & STARTMASK) == STARTVAL){ receivestate = 1; recdata[0] = c; nextpacketbyte = 1; break; } else { receivestate = 0; displaychar(c,1); } break; } if(nextextrabyte < EXTRABUFSIZE) /* dump extras */ recextra[nextextrabyte] = c; nextextrabyte++; if(nextextrabyte == numextrabytes){ /* done */ receivestate = 0; return(numextrabytes <= EXTRABUFSIZE && checksum(recdata) == recdata[1]); } break; } } return(FALSE); } /* output a local message and send it over the modem */ putmsg(s) char *s; { outerr(s); while(*s != 0){ putmodem(*s); s++; } } /* execute commands received over the modem. If waiting for high level * ack, ignore command and undo save commands */ static void domodemcommand(){ int s,num,i,command,x,y; char buf[100]; command = (recdata[2] >> 4) & 0x7; #ifndef PCMIN if(debug != 0){ sprintf(buf,"got cmd %d\n",command); outerr(buf); } #endif if(recdata[2] & 8){ /* reserved bit don't know what this means */ putdenial(); return; } switch(command){ case DENIALCMD: putack(); takeback(); denycount++; if(denycount > 3){ turnoffcplay(); outerr("Opponent denying moves. Stopping computer play"); } break; case QUERYCMD: putresponse(); break; case RESETCMD: if(compmoveinprogress){ /* can't take back moves while computer is thinking */ putdenial(); putmsg("\n\nComputer thinking, command denied.\n"); break; } nextquery = 0; putack(); mailgame(); #ifdef PC mouseShow(HIDECURSOR); /* hide the mouse */ #endif newgame(); /* initialize for new game */ #ifdef PC if(havemouse)mouseShow(SHOWCURSOR); #endif break; case TAKEBACKCMD: /* take back */ if(compmoveinprogress){ /* can't take back moves while computer is thinking */ putdenial(); putmsg("\n\nComputer thinking, command denied.\n"); break; } num = recdata[3] & 0x7f; num |= (recdata[2] & 7) << 7; if(num > msptr-handicap){ putdenial(); putmsg("\n\nCan't take back handicap stones.\n"); break; } putack(); #ifdef PC mouseShow(HIDECURSOR); #endif if(num == 0)break; clearerror(); outerr("Retracting move\n"); for(i = 0; i < num; ++i){ if(msptr > 0) retractmove(FALSE); /* retract but don't send */ } outerr("Done\n"); gamestatus(); #ifdef PC if(havemouse)mouseShow(SHOWCURSOR); #endif break; case MOVECMD: if(compmoveinprogress){ /* can't make moves while computer is thinking */ putdenial(); putmsg("\n\nComputer thinking, command denied.\n"); break; } mvcolor[msptr] = (recdata[2] & 4) >> 2; s = ((int)recdata[2] << 7) | (recdata[3] & 0x7f); s &= 0x1ff; if(s == 0) mvs[msptr] = PASS; else { s--; x = s%boardsize; y = s/boardsize; mvs[msptr] = boardsize*(boardsize-y-1)+x; } if(mvs[msptr] != PASS && (mvs[msptr] >= boardsquare || mvs[msptr] < 0 || S_COLOR(mvs[msptr]) != NOCOLOR)){ /* got illegal move */ putdenial(); putmsg("\n\nIllegal move: denied.\n"); break; } putack(); #ifdef PC mouseShow(HIDECURSOR); #endif check(FALSE); /* make the move */ gamestatus(); #ifdef PC if(havemouse)mouseShow(SHOWCURSOR); #endif break; case RESPONDCMD: num = recdata[3] & 0x7f; num |= (recdata[2] & 7) << 7; nextquery++; /* prepare to send next query */ switch(lastquerysent){ case QUERYBUF: hisbuffersize = num*16 + 4; break; case QUERYSTRING: hesupportsstring = num; break; case QUERYMULTI: hesupportsmulti = num; break; case QUERYHANDICAP: hishandicap = num; if(hishandicap == 1 && handicap != 0 || hishandicap > 1 && hishandicap != handicap){ if(hishandicap == 1)hishandicap = 0; sprintf(buf,"His handicap (%d) and your handicap (%d) are different!",hishandicap,handicap); outerr(buf); } break; case QUERYBOARDSIZE: hisboardsize = num; if(hisboardsize != 0 && hisboardsize != boardsize){ sprintf(buf,"His board size (%d) and your board size (%d) are different!",hisboardsize,boardsize); outerr(buf); } break; case QUERYRULES: hisrules = num; if(hisrules != 0 && hisrules-1 != chineseflag) outerr("He is playing different rules than you"); break; case QUERYWHO: whoishe = num; outerr("Connected to "); switch(whoishe){ case NEMESIS: outerr("Nemesis"); break; case MFGO: outerr("Many Faces of Go"); break; case SMARTGO: outerr("Smart Go Board"); break; case GOLIATH: outerr("Goliath"); break; case GOINT: outerr("Go Intellect"); break; case STARPOL: outerr("Star of Poland"); break; default: outerr("Unknown"); break; } break; } putack(); /* might send next query */ break; default: putdenial(); /* tell him I don't understand his command */ } } /* take back the last command */ static void takeback(){ list_t ptr; #ifdef PC mouseShow(HIDECURSOR); #endif for(ptr = undocommands; ptr != EOL; ptr = link[ptr]) if(list[ptr] == TAKEBACK) if(msptr > 0){ retractmove(FALSE); /* retract, but don't send */ } else { mvcolor[msptr] = (list[ptr] & WHITEUNDO) == WHITEUNDO; mvs[msptr] = list[ptr] & 511; check(FALSE); gamestatus(); } #ifdef PC if(havemouse)mouseShow(SHOWCURSOR); #endif killist(&undocommands); gamestatus(); } /************************************************************************** * * Serial com port for IBM-PC * **************************************************************************/ # define RS232 0x14 /* serial communication BIOS interrrupt */ # define COMBUFSIZE 68 char icombuf[COMBUFSIZE]; int icbfront = 0, icbback = 0; int vector = 0; /* interrupt vector substituted */ unsigned int portaddr; /* com port base address */ unsigned int portnum; /* com port number */ int mask; /* interrupt enable mask for 8259 */ void modemintoff(){ int tmp; if(!modemconnected)return; _disable(); tmp = inp(0x21); tmp &= ~mask; /* turn off com port interrupt mask */ outp(0x21,tmp); _enable(); } void modeminton(){ int tmp; if(!modemconnected)return; _disable(); tmp = inp(0x21); tmp |= mask; /* turn off com port interrupt mask */ outp(0x21,tmp); _enable(); } static void (_interrupt _far *old)(); /* old handler address */ /* handle COM port interrupt for "Data received". * IMPORTANT NOTE: This interrupt handler assumes a vaid stack exists. * It will cause the PC to crash if called when there is not enough * stack space (about 20 or 30 bytes). In particular if it is called * while the microsoft overlay manager is executing I have seen it cause * a crash. I recommend disabling the com port interrupts before loading * an overlay. Alternatively, you can rewrite it to use a local stack * which requires assembler. */ void _interrupt _far comhandler(){ int intid,intr,lsr,msr,count = 0;; _enable(); /* enable higher priority interrupts */ while(TRUE){ /* process all pending interrupts */ intid = inp(portaddr+2); /* get interrupt type */ lsr = inp(portaddr+3); /* clear any overrun condition */ msr = inp(portaddr+6); /* clear any modem signal condition */ if((intid & 1) == 1){ /* no interrupts */ outp(0x20,0x20); /* reenable interrupts */ return; } if(intid >= 4){ /* reenable interrupt if needed */ intr = inp(portaddr+1); if((intr & 2) == 0)outp(portaddr+1,intr | 2); } if(intid == 4){ /* receive data available */ icombuf[icbfront] = inp(portaddr); /* stick char in front of queue */ icbfront++; if(icbfront == COMBUFSIZE) icbfront = 0; if(icbfront == icbback){ /* overrun throw away old char */ icbfront--; if(icbfront == -1)icbfront = COMBUFSIZE-1; } } } } /* initialize modem. comport contains the com port number (1-2) */ /* baudrate is 0-300, 1-1200, 2-2400, 3-4800, 4-9600 */ /* return FALSE if no com port */ initmodem(comport,baudrate,modemstring) int comport,baudrate; char *modemstring; { union REGS regs; char buf[80],dummy,tmp; unsigned int init = _COM_CHR8 | _COM_STOP1 | _COM_NOPARITY; /* no parity, 1 stop bit, 8 bits */ switch(baudrate){ case 0: init |= _COM_300; break; case 1: init |= _COM_1200; break; case 2: init |= _COM_2400; break; case 3: init |= _COM_4800; break; case 4: init |= _COM_9600; break; } if(comport == 1){ portnum = 0; portaddr = *((short far *)0x400000L); vector = 0xc; mask = 0x10; } else { portnum = 1; portaddr = *((short far *)0x400002L); vector = 0xb; mask = 0x8; } if(portaddr == 0){ outerr("Serial port not found"); return(FALSE); } old = _dos_getvect(vector); /* save old vector */ _disable(); _dos_setvect(vector,comhandler); /* install my vector */ _enable(); _bios_serialcom(_COM_INIT,portnum,init); /* initialize uart */ _disable(); outp(portaddr+4,11); /* initialize modem control register */ /* bit 3 enables ints */ /* bit 0,1 DTR and RTS */ outp(portaddr+1,1); /* enable input interrupts */ tmp = inp(0x21); /* enable interrupt through 8259 */ tmp &= ~mask; /* clear interrupt mask bit */ outp(0x21,tmp); _enable(); modemconnected = TRUE; while(*modemstring != 0)putmodem(*modemstring++); /* put the call or answer string */ while(getmodem(&dummy)); /* eat the chars echoed from the modem */ } /* turn off the modem and remove the interrupt handler */ offmodem(){ unsigned char tmp; if(vector != 0){ _disable(); outp(portaddr+1,0); /* disable all serial interrupts */ outp(portaddr+4,0); /* turn off modem controls */ tmp = inp(0x21); tmp |= mask; /* turn off com port interrupt mask */ outp(0x21,tmp); _dos_setvect(vector,old); /* restore interrupt vector */ vector = 0; _enable(); outerr("Modem disabled"); } if(modemconnected && hayesflag){ putmodem(modemclose); putmodem('\r'); hayesflag = FALSE; } modemconnected = FALSE; waitinghighack = FALSE; } /* get a charcter from the serial port input buffer. Return FALSE if * the buffer is empty */ getmodem(c) char *c; { if(icbback == icbfront)return(FALSE); /* empty buffer */ *c = icombuf[icbback]; _disable(); icbback++; if(icbback == COMBUFSIZE)icbback = 0; _enable(); return(TRUE); } /* send char c to output buffer */ putmodem(c) unsigned char c; { int status; do { status = inp(portaddr+5); } while(!(status & 0x20)); /* wait for transmit register empty */ outp(portaddr,c); }
The material on this page was supplied by David Fotland.