From 21795645b15926d5d22c5bd8d2e4453d7cbb5fb8 Mon Sep 17 00:00:00 2001 From: Dan Ballard Date: Sun, 13 Mar 2011 16:44:17 -0700 Subject: [PATCH] Initial import of our Go/GTP server project --- LICENSE | 340 +++++++++++++++++++ README | 4 + ai_go.sql | 68 ++++ demo.bot | 6 + goclient.py | 223 +++++++++++++ goserver.py | 296 +++++++++++++++++ gtp2ip.py | 165 +++++++++ gtpserver.py | 826 ++++++++++++++++++++++++++++++++++++++++++++++ netpipe.py | 38 +++ website/index.php | 304 +++++++++++++++++ 10 files changed, 2270 insertions(+) create mode 100644 LICENSE create mode 100644 README create mode 100644 ai_go.sql create mode 100644 demo.bot create mode 100755 goclient.py create mode 100755 goserver.py create mode 100755 gtp2ip.py create mode 100755 gtpserver.py create mode 100644 netpipe.py create mode 100644 website/index.php diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3912109 --- /dev/null +++ b/LICENSE @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/README b/README new file mode 100644 index 0000000..27cd4bd --- /dev/null +++ b/README @@ -0,0 +1,4 @@ +Most usage instructions at: +http://ai.mindstab.net/wiki/index.php/Goserver + +If setting up a stats website, place the website directory somewhere web visible, set up a MySQL database as outlined in ai_go.sql, and configure the DB options in website/index.php and gtpserver.py accordingly. diff --git a/ai_go.sql b/ai_go.sql new file mode 100644 index 0000000..ef631d7 --- /dev/null +++ b/ai_go.sql @@ -0,0 +1,68 @@ +-- MySQL dump 10.11 +-- +-- Host: localhost Database: ai_go +-- ------------------------------------------------------ +-- Server version 5.0.54-log + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES latin1 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Table structure for table `bots` +-- + +DROP TABLE IF EXISTS `bots`; +SET @saved_cs_client = @@character_set_client; +SET character_set_client = utf8; +CREATE TABLE `bots` ( + `id` int(11) NOT NULL auto_increment, + `name` varchar(255) default NULL, + `version` varchar(255) default NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `id` (`id`), + UNIQUE KEY `id_2` (`id`), + UNIQUE KEY `id_3` (`id`) +) ENGINE=MyISAM AUTO_INCREMENT=15 DEFAULT CHARSET=latin1; +SET character_set_client = @saved_cs_client; + +-- +-- Table structure for table `matchs` +-- + +DROP TABLE IF EXISTS `matchs`; +SET @saved_cs_client = @@character_set_client; +SET character_set_client = utf8; +CREATE TABLE `matchs` ( + `id` int(11) NOT NULL auto_increment, + `black_id` int(11) default NULL, + `white_id` int(11) default NULL, + `score` float default NULL, + `date` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP, + `black_time` varchar(255) default NULL, + `white_time` varchar(255) default NULL, + `sgffile` varchar(255) default NULL, + `handicap` int(11) default NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `id` (`id`), + UNIQUE KEY `id_2` (`id`) +) ENGINE=MyISAM AUTO_INCREMENT=158 DEFAULT CHARSET=latin1; +SET character_set_client = @saved_cs_client; + + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + +-- Dump completed on 2008-06-03 16:11:48 diff --git a/demo.bot b/demo.bot new file mode 100644 index 0000000..e33a8ee --- /dev/null +++ b/demo.bot @@ -0,0 +1,6 @@ +program=/usr/bin/demobot +handle-net=false +name=Demo Bot +version=1.0 +author=Some Guy +password=abcd diff --git a/goclient.py b/goclient.py new file mode 100755 index 0000000..5e2d4df --- /dev/null +++ b/goclient.py @@ -0,0 +1,223 @@ +#!/usr/bin/python + +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# This program is part of the Mindstab GO server package # +# # +# See http://ai.mindstab.net/ for more information. # +# # +# Copyright 2007 by Dan Ballard, Robert Hausch, and ai.mindstab.net # +# # +# This program is free software; you can redistribute it and/or # +# modify it under the terms of the GNU General Public License # +# as published by the Free Software Foundation - version 2. # +# # +# This program is distributed in the hope that it will be # +# useful, but WITHOUT ANY WARRANTY; without even the implied # +# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # +# PURPOSE. See the GNU General Public License in file COPYING # +# for more details. # +# # +# You should have received a copy of the GNU General Public # +# License along with this program; if not, write to the Free # +# Software Foundation, Inc., 51 Franklin Street, Fifth Floor, # +# Boston, MA 02111, USA. # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + + +from getopt import * +import sys +import string +import re +import os +import netpipe +import socket +import time + +debug = 1 +children = [] + +def readbotfile(botfile): + bot = {} + file = open(botfile, "r") + for line in file: + #line = file.readline() + line = line.strip("\n") + if line == "": + continue + (opt, value) = line.split("=") + bot[opt] = value + file.close() + if not bot.has_key('handle-net'): + bot['handle-net'] = 'false' + return bot + +def houseloop(np, bot): + quit = False + while not quit: + cmd = np.receive() + #print cmd + cmds = cmd.split(" ") + #print cmds + if cmds[0] == "quit": + #print "cmd == quit" + #print cmds[1] + " <=> " + botinfo["password"] + if cmds[1] == bot["password"]: + np.send("yes") + quit = True + else: + np.send("no") + elif cmds[0] == "pid": + print "ID: " + cmds[1] + elif cmds[0] == "ping": + np.send("pong") + elif cmds[0] == "play": + address = cmds[1] + port = cmds[2] + print "Game accepted @ %s-%s-%s %s:%s:%s\n" % time.localtime()[:6] + spawnbot(bot["program"], bot["handle-net"], address, port) + #os.spawnlp(os.P_NOWAIT, "./gtp2ip.py", "./gtp2ip.py", "--program", bot["program"], "--ip", address, "--port", port) + + +def spawnbot(program, handleNet, address, port, feedback = False): + global children + reapchildren() + if handleNet == 'true': + print "starting wrapper " + program + " " + address + " " +port + pid = os.spawnlp(os.P_NOWAIT, program, program, address, port) + elif feedback: + pid = os.spawnlp(os.P_WAIT, "./gtp2ip.py", "./gtp2ip.py", "--program", program, "--ip", address, "--port", port, "--feedback") + else: + pid = os.spawnlp(os.P_NOWAIT, "./gtp2ip.py", "./gtp2ip.py", "--program", program, "--ip", address, "--port", port) + children.append(pid) + +def reapchildren(): + global children + for child in children: + (pid, exit) = os.waitpid(child, os.WNOHANG) + if pid != 0: + children.remove(child) + + + +usagestr = """ + +goclient.py --server [server name] + + --list List house bots on server + --house Register a house bot on the server + --guest --vs Set your bot to play agaist a house bot + --quit --pass Remove one of your house bots from the server + --match --vs Play two house bots against each other + [--games ] ... for number of games + + --ip + --port + +""" + + +port = "10000" +ip = "" +botfile = "" +cmd = "" +vs = "" +passwd = "" +pid = "0" +match = "0" +games = "1" +server = "" + +def usage(): + global usagestr + print usagestr + sys.exit(1) + + +try: + (opts, params) = getopt(sys.argv[1:], "", + ["port=", + "ip=", + "server=", + "list", + "house=", + "guest=", + "vs=", + "quit=", + "pass=", + "match=", + "games=", + ]) +except: + usage() + +for opt, value in opts: + if opt == "--port": + port = value + elif opt == "--ip": + ip = value + elif opt == "--server": + server = value + ip = socket.gethostbyname(server) + elif opt == "--list": + cmd = "list" + elif opt == "--house": + cmd = "house" + botfile = value + elif opt == "--guest": + cmd = "guest" + botfile = value + elif opt == "--quit": + cmd = "quit" + pid = value + elif opt == "--vs": + vs = value + elif opt == "--pass": + passwd = value + elif opt == "--match": + cmd = "match" + match = value + elif opt == "--games": + games = value + + +if port == 0 or ip == "" or cmd == "" or (cmd == "guest" and vs == "") or (cmd == "match" and vs == ""): + usage() + + +np = netpipe.netpipe() +np.connect(ip,int(port)) + +if cmd == "list": + np.send("list") + list = np.receive() + print "ID\tName\t\tVersion\t\tAuthor" + for botstr in list.split("\n"): + if botstr == "": + continue + (pid,name,version,author) = botstr.split(",") + print "%s\t%s\t\t%s\t\t%s" % (pid,name,version,author) +elif cmd == "house": + bot = readbotfile(botfile) + botinfo = "" + for opt in bot.keys() : + botinfo = botinfo + opt + "=" + bot[opt] +"," + np.send("house " + botinfo) + houseloop(np, bot) +elif cmd == "quit": + np.send("quit " + pid + " " + passwd) +elif cmd == "match": + np.send("match " + match + " " + vs + " " + games) + resp = np.receive() + print resp +elif cmd == "guest": + bot = readbotfile(botfile) + np.send("guest " + vs + " " + games) + resp = np.receive() + resps = resp.split(" ") + if resps[0] == "error": + print resps + elif resps[0] == "play": + spawnbot(bot["program"], bot["handle-net"], resps[1], resps[2], True) + + +np.close() diff --git a/goserver.py b/goserver.py new file mode 100755 index 0000000..49bed2e --- /dev/null +++ b/goserver.py @@ -0,0 +1,296 @@ +#!/usr/bin/python + +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# This program is part of the Mindstab GO server package # +# # +# See http://ai.mindstab.net/ for more information. # +# # +# Copyright 2007 by Dan Ballard, Robert Hausch, and ai.mindstab.net # +# # +# This program is free software; you can redistribute it and/or # +# modify it under the terms of the GNU General Public License # +# as published by the Free Software Foundation - version 2. # +# # +# This program is distributed in the hope that it will be # +# useful, but WITHOUT ANY WARRANTY; without even the implied # +# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # +# PURPOSE. See the GNU General Public License in file COPYING # +# for more details. # +# # +# You should have received a copy of the GNU General Public # +# License along with this program; if not, write to the Free # +# Software Foundation, Inc., 51 Franklin Street, Fifth Floor, # +# Boston, MA 02111, USA. # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + + + +from getopt import * +import sys +import string +import re +import socket +import os +import netpipe +import time +import signal +#for get_ip_address +import fcntl +import struct + + +debug = 1 +house = {} +lastpid = 0 + +portpool = [10001, 10002, 10003, 10004, 10005, 10006, 10007, 10008, 10009, 10010] +currentport = 0 +children = [] + + +#borrowed from the internet +def get_ip_address(ifname): + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + return socket.inet_ntoa(fcntl.ioctl( + s.fileno(), + 0x8915, # SIOCGIFADDR + struct.pack('256s', ifname[:15]) + )[20:24]) + + +def getnextport(): + global portpool + global currentport + port = portpool[currentport] + currentport = currentport + 1 + if currentport >= len(portpool): + currentport = 0 + return port + +class housebot: + def __init__(self, np, bot): + self.np = np + self.bot = bot + + def getval(self, name): + val = self.bot[name] + if val != None: + return val + else: + return "" + + def setpid(self, pid): + self.bot[pid] = pid + + def playgame(self, address, port): + cmd = "play " + address + " " + port + self.np.send(cmd) + + def cleanup(self): + self.np.close() + + def quit(self, passwd): + self.np.send("quit " + passwd) + resp = self.np.receive() + return resp == "yes" + + def keepalive(self): + self.np.send("ping") + resp = self.np.receive() + return resp == "pong" + +def handleconnection(np): + global house + try: + cmd = np.receive() + except: + np.close() + return + + + cmds = cmd.split(" ") + if cmds[0] == "house": + handlehouse(np, cmd[6:]) + elif cmds[0] == "guest": + handleguest(np, cmds) + elif cmds[0] == "quit": + handlequit(cmds) + np.close() + elif cmds[0] == "list": + botlist = "" + for pid in house.keys(): + botlist = botlist + str(pid) + "," + house[pid].getval("name") + "," + house[pid].getval("version") + "," + house[pid].getval("author") + "\n" + np.send(botlist) + np.close() + elif cmds[0] == "match": + handlematch(cmds, np) + np.close() + +def parsebotstr(botstr): + bot = {} + for info in botstr.split(","): + try: + (opt, val) = info.split("=") + except: + continue + bot[opt] = val + return bot + +def handlehouse(np, botstr): + hbot = housebot(np, parsebotstr(botstr)) + pid = registerhousebot(hbot) + np.send("pid " + str(pid)) + + +def registerhousebot(hbot): + global house + global lastpid + house[lastpid] = hbot + hbot.setpid(lastpid) + lastpid = lastpid + 1 + return lastpid -1 + + +def removehousebot(pid): + global house + house[pid].cleanup() + del house[pid] + +def handlequit(cmds): + global house + pid = int(cmds[1]) + if not pid in house.keys(): + return + passwd = cmds[2] + if house[pid].getval("password") == passwd: + if house[pid].quit(passwd): + removehousebot(pid) + +def spawngtpserver(port, games): + global children + reapchildren() + pid = os.spawnlp(os.P_NOWAIT, "./gtpserver.py", "./gtpserver.py", "--port", str(port), "--games", games) #, "--sgfbase", "gtpserver") # NO SGF STORING FOR NOW + # let the server start + time.sleep(1) + children.append(pid) + return pid + +def reapchildren(): + global children + for child in children: + (pid, exit) = os.waitpid(child, os.WNOHANG) + if pid != 0: + children.remove(child) + + +def abortmatch(pid, botid, np): + removehousebot(botid) + os.kill(pid, signal.SIGKILL) + np.send("error NOID " + str(botid)) + +def handlematch(cmds, np): + global house, ip_address + id1 = int(cmds[1]) + id2 = int(cmds[2]) + if id1 not in house.keys(): + np.send("error NOID " + cmds[1]) + return + if id2 not in house.keys(): + np.send("error NOID " + cmds[2]) + return + games = cmds[3] + port = getnextport() + pid = spawngtpserver(port, games) + try: + house[id1].playgame(ip_address, str(port)) + except: + print "abort 1" + abortmatch(pid, id1, np) + return + try: + house[id2].playgame(ip_address, str(port)) + except: + print "abort 2" + abortmatch(pid, id2, np) + return + np.send("ok") + +def handleguest(np, cmds): + global house, ip_address + id1 = int(cmds[1]) + if id1 not in house.keys(): + np.send("error NOID " + cmds[1]) + return + games = cmds[2] + port = getnextport() + pid = spawngtpserver(port, games) + try: + house[id1].playgame(ip_address, str(port)) + except: + abortmatch(pid, id1, np) + return + np.send("play " + ip_address + " " + str(port)) + + + +ip_address = get_ip_address("eth0") +port = 10000 + +print "'" + ip_address + "'" + +def usage(): + print "server.py --port \n" + sys.exit(1) + + +try: + (opts, params) = getopt(sys.argv[1:], "", + ["port=", + ]) +except: + usage(); + +for opt, value in opts: + if opt == "--port": + port = value + +if port == 0: + usage() + + + +serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +# Behave better after crash +serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +#print socket.gethostname() +#print socket.gethostbyname(socket.gethostname()) +serversocket.bind( ('', int(port))) #(socket.gethostname(), int(port))) + +def clean_house(): + reapchildren() + for pid in house.keys(): + try: + # print "trying bot[%d] %s\n" % (pid, house[pid].getval('name')) + if not house[pid].keepalive(): + print "failed keepalive\n" + removehousebot(pid) + except: + print "exception to keepalive\n" + removehousebot(pid) + +serversocket.settimeout(30) +serversocket.listen(2) + +while 1: + try: + (socket, address) = serversocket.accept() + except: + clean_house() + socket = 0 + if socket: + np = netpipe.netpipe(socket) + handleconnection(np) + +os.kill(pid, signal.SIGKILL) +closeconnections() +serversocket.close() diff --git a/gtp2ip.py b/gtp2ip.py new file mode 100755 index 0000000..414ee69 --- /dev/null +++ b/gtp2ip.py @@ -0,0 +1,165 @@ +#!/usr/bin/python + +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# This program is part of the Mindstab GO server package # +# # +# See http://ai.mindstab.net/ for more information. # +# # +# Copyright 2007 by Dan Ballard, Robert Hausch, and ai.mindstab.net # +# # +# Uses GTP_connection class from GnuGo so licenced under GPL3 # +# Origional licence below # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# This program is distributed with GNU Go, a Go program. # +# # +# Write gnugo@gnu.org or see http://www.gnu.org/software/gnugo/ # +# for more information. # +# # +# Copyright 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006 and 2007 # +# by the Free Software Foundation. # +# # +# This program is free software; you can redistribute it and/or # +# modify it under the terms of the GNU General Public License # +# as published by the Free Software Foundation - version 3, # +# or (at your option) any later version. # +# # +# This program is distributed in the hope that it will be # +# useful, but WITHOUT ANY WARRANTY; without even the implied # +# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # +# PURPOSE. See the GNU General Public License in file COPYING # +# for more details. # +# # +# You should have received a copy of the GNU General Public # +# License along with this program; if not, write to the Free # +# Software Foundation, Inc., 51 Franklin Street, Fifth Floor, # +# Boston, MA 02111, USA. # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + + +from getopt import * +import socket +import popen2 +import sys +import netpipe +import re +import os + +debug = 0 +#print "%s gtp2ip.py" % (os.getpid()) + +class GTP_connection: + + def __init__(self, command): + try: + infile, outfile = popen2.popen2(command) + except: + print "popen2 failed" + sys.exit(1) + self.infile = infile + self.outfile = outfile + + def exec_cmd(self, cmd): + global debug + global feedback + + if debug: + sys.stderr.write("GTP command: " + cmd + "\n") + if feedback: + #if cmd[0:4] == "play": + print cmd + if cmd == "protocol_version": + result = "= gtp2ip-0.1\n" + elif cmd[0:10] == "game_score": + if feedback: + print "Game Score: " + cmd[10:] + result = "= \n" + else: + try: + self.outfile.write(cmd + "\n\n") + self.outfile.flush() + result = "" + line = self.infile.readline() + if line == "": + print "ERROR: bot crashed" + return "quit\n" + while line != "\n": + result = result + line + line = self.infile.readline() + except: + print "ERROR: bot crashed" + return "quit" + if debug: + sys.stderr.write("Reply: '" + result + "'\n") + if feedback: + #if line[0:4] == "play": + print result + + return result + + + +def usage(): + print "gtp2ip.py --program '' --ip --port \n\n" + sys.exit(1) + + +program = "" +port = 0 +ip = 0 +feedback = False + +#print sys.argv + +try: + (opts, params) = getopt(sys.argv[1:], "", + ["program=", + "port=", + "ip=", + "feedback", + ]) +except: + print "except error" + usage() + + +for opt, value in opts: + if opt == "--program": + program = value + elif opt == "--port": + port = value + elif opt == "--ip": + ip = value + elif opt == "--feedback": + feedback = True + +if program == "" or ip == 0 or port == 0: + usage() + + +gtp = GTP_connection(program) +#print "GTP active" +np = netpipe.netpipe() +np.connect(ip,int(port)) +#print "np active" + +while 1: + try: + cmd = np.receive() + except: + print "ERROR: socket error" + sys.exit(-1) + result = gtp.exec_cmd(cmd) + if result == "quit": + np.close() + sys.exit(-1) + try: + np.send(result) + except: + print "ERROR: socket error" + sys.exit(-1) + if cmd == 'quit': + np.close() + sys.exit(0) + diff --git a/gtpserver.py b/gtpserver.py new file mode 100755 index 0000000..2be2c25 --- /dev/null +++ b/gtpserver.py @@ -0,0 +1,826 @@ +#!/usr/bin/python + +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# This program is part of the Mindstab GO server package # +# # +# See http://ai.mindstab.net/ for more information. # +# # +# Copyright 2007 by Dan Ballard, Robert Hausch, and ai.mindstab.net # +# # +# It is mostly just a modification of twogtp.py from GnuGo, # +# origional licence below # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# This program is distributed with GNU Go, a Go program. # +# # +# Write gnugo@gnu.org or see http://www.gnu.org/software/gnugo/ # +# for more information. # +# # +# Copyright 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006 and 2007 # +# by the Free Software Foundation. # +# # +# This program is free software; you can redistribute it and/or # +# modify it under the terms of the GNU General Public License # +# as published by the Free Software Foundation - version 3, # +# or (at your option) any later version. # +# # +# This program is distributed in the hope that it will be # +# useful, but WITHOUT ANY WARRANTY; without even the implied # +# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # +# PURPOSE. See the GNU General Public License in file COPYING # +# for more details. # +# # +# You should have received a copy of the GNU General Public # +# License along with this program; if not, write to the Free # +# Software Foundation, Inc., 51 Franklin Street, Fifth Floor, # +# Boston, MA 02111, USA. # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + + + +from getopt import * +import sys +import string +import re +import socket +import netpipe +import os +import time +import MySQLdb + +debug = 0 + +# set to True if hosting statss website +use_sql=False; +sql_db = "ai_go"; +sql_user = "ai_go"; +sql_pw = "password"; +sgf_to_png = "/path/to/gogui-thumbnailer"; + +if (use_sql): + conn = MySQLdb.connect (host = "localhost", user = sql_user, passwd = sql_pw, db = sql_db) + cursor = conn.cursor () + + +def coords_to_sgf(size, board_coords): + global debug + + board_coords = string.lower(board_coords) + if board_coords[:4] == "pass": + return "" + if debug: + print "Coords: <" + board_coords + ">" + letter = board_coords[0] + digits = board_coords[1:] + if letter > "i": + sgffirst = chr(ord(letter) - 1) + else: + sgffirst = letter + sgfsecond = chr(ord("a") + int(size) - int(digits)) + return sgffirst + sgfsecond + + + +class GTP_connection: + + # + # Class members: + # outfile File to write to + # infile File to read from + + def __init__(self, np): + self.np = np + + + def exec_cmd(self, cmd): + global debug + + if debug: + sys.stderr.write("GTP command: " + cmd + "\n") + + try: + self.np.send(cmd) + result = self.np.receive() + except: + return "ERROR: socket error" + + #if debug: + # sys.stderr.write("Reply: " + line + "\n") + + # Remove trailing newline from the result + if result[-1] == "\n": + result = result[:-1] + + if len(result) == 0: + return "ERROR: len = 0" + if (result[0] == "?"): + return "ERROR: GTP Command failed: " + result[2:] + if (result[0] == "="): + return result[2:] + return "ERROR: Unrecognized answer: " + result + + +class GTP_player: + + # Class members: + # connection GTP_connection + + def __init__(self, command): + global use_sql + self.connection = GTP_connection(command) + protocol_version = self.connection.exec_cmd("protocol_version") + if protocol_version[:5] != "ERROR": + self.protocol_version = protocol_version + else: + self.protocol_version = "1" + self.name = self.connection.exec_cmd("name").strip() + self.version = self.connection.exec_cmd("version").strip() + + if use_sql and self.name[:5] != "ERROR": + self.insert_bot_into_db() + + def insert_bot_into_db(self): + global cursor + cursor.execute("SELECT * from bots where name=\"%s\" and version=\"%s\";" % (self.name, self.version)) + if cursor.rowcount == 0: + cursor.execute("INSERT into bots (name, version) values(\"%s\", \"%s\");" %(self.name, self.version)) + cursor.execute("SELECT * from bots where name=\"%s\" and version=\"%s\";" % (self.name, self.version)) + row = cursor.fetchone () + self.bot_id = int(row[0]) + + def send_score(self, score): + return self.connection.exec_cmd("game_score " + score) + + def is_known_command(self, command): + resp = self.connection.exec_cmd("known_command " + command) + return resp == "true" or resp[:1] == "1" + + def genmove(self, color): + if color[0] in ["b", "B"]: + command = "black" + elif color[0] in ["w", "W"]: + command = "white" + if self.protocol_version == "1": + command = "genmove_" + command + else: + command = "genmove " + command + + return self.connection.exec_cmd(command) + + def black(self, move): + if self.protocol_version == "1": + self.connection.exec_cmd("black " + move) + else: + self.connection.exec_cmd("play black " + move) + + def white(self, move): + if self.protocol_version == "1": + self.connection.exec_cmd("white " + move) + else: + self.connection.exec_cmd("play white " + move) + + def komi(self, komi): + self.connection.exec_cmd("komi " + komi) + + def boardsize(self, size): + self.connection.exec_cmd("boardsize " + size) + if self.protocol_version != "1": + self.connection.exec_cmd("clear_board") + + def handicap(self, handicap, handicap_type): + if handicap_type == "fixed": + result = self.connection.exec_cmd("fixed_handicap %d" % (handicap)) + else: + result = self.connection.exec_cmd("place_free_handicap %d" + % (handicap)) + + return string.split(result, " ") + + def loadsgf(self, endgamefile, move_number): + self.connection.exec_cmd(string.join(["loadsgf", endgamefile, + str(move_number)])) + + def list_stones(self, color): + return string.split(self.connection.exec_cmd("list_stones " + color), " ") + + def quit(self): + return self.connection.exec_cmd("quit") + + def showboard(self): + board = self.connection.exec_cmd("showboard") + if board and (board[0] == "\n"): + board = board[1:] + return board + + def get_random_seed(self): + result = self.connection.exec_cmd("get_random_seed") + if result[:5] == "ERROR": + return "unknown" + return result + + def set_random_seed(self, seed): + self.connection.exec_cmd("set_random_seed " + seed) + + def get_program_name(self): + return self.connection.exec_cmd("name") + " " + \ + self.connection.exec_cmd("version") + + def final_score(self, score_file): + print "/usr/games/bin/gnugo --score finish -l " + score_file + " 2> /dev/null" + score = os.popen("/usr/games/bin/gnugo --score finish -l " + score_file + " 2> /dev/null").read() + #print "SCORE: '" + score + "'" + result = score[0] + arr = score.split(" ") + result = score[0] + '+' + arr[3] + "\n" + print "RESULT '" + result + "'" + return result + + def score(self): + return self.final_score(self) + + def cputime(self): + if (self.is_known_command("cputime")): + return self.connection.exec_cmd("cputime").strip() + else: + return "0" + + +class GTP_game: + + # Class members: + # whiteplayer GTP_player + # blackplayer GTP_player + # size int + # komi float + # handicap int + # handicap_type string + # handicap_stones int + # moves list of string + # resultw + # resultb + + def __init__(self, whitecommand, blackcommand, size, komi, handicap, + handicap_type, endgamefile): + self.whiteplayer = GTP_player(whitecommand) + self.blackplayer = GTP_player(blackcommand) + self.size = size + self.komi = komi + self.handicap = handicap + self.handicap_type = handicap_type + self.endgamefile = endgamefile + self.sgffilestart = "" + if endgamefile != "": + self.init_endgame_contest_game() + else: + self.sgffilestart = "" + + def init_endgame_contest_game(self): + infile = open(self.endgamefile) + if not infile: + print "Couldn't read " + self.endgamefile + sys.exit(2) + sgflines = infile.readlines() + infile.close + size = re.compile("SZ\[[0-9]+\]") + move = re.compile(";[BW]\[[a-z]{0,2}\]") + sgf_start = [] + for line in sgflines: + match = size.search(line) + if match: + self.size = match.group()[3:-1] + match = move.search(line) + while match: + sgf_start.append("A" + match.group()[1:]) + line = line[match.end():] + match = move.search(line) + self.endgame_start = len(sgf_start) - endgame_start_at + self.sgffilestart = ";" + string.join( + sgf_start[:self.endgame_start-1], "") + "\n" + if self.endgame_start % 2 == 0: + self.first_to_play = "W" + else: + self.first_to_play = "B" + + def get_position_from_engine(self, engine): + black_stones = engine.list_stones("black") + white_stones = engine.list_stones("white") + self.sgffilestart = ";" + if len(black_stones) > 0: + self.sgffilestart += "AB" + for stone in black_stones: + self.sgffilestart += "[%s]" % coords_to_sgf(self.size, stone) + self.sgffilestart += "\n" + if len(white_stones) > 0: + self.sgffilestart += "AW" + for stone in white_stones: + self.sgffilestart += "[%s]" % coords_to_sgf(self.size, stone) + self.sgffilestart += "\n" + + def writesgf(self, sgffilename): + "Write the game to an SGF file after a game" + + size = self.size + outfile = open(sgffilename, "w") + if not outfile: + print "Couldn't create " + sgffilename + return + black_name = self.blackplayer.get_program_name() + white_name = self.whiteplayer.get_program_name() + black_seed = self.blackplayer.get_random_seed() + white_seed = self.whiteplayer.get_random_seed() + handicap = self.handicap + komi = self.komi + result = 0#self.resultw + + outfile.write("(;GM[1]FF[4]RU[Japanese]SZ[%s]HA[%s]KM[%s]RE[%s]\n" % + (size, handicap, komi, result)) + outfile.write("PW[%s (random seed %s)]PB[%s (random seed %s)]\n" % + (white_name, white_seed, black_name, black_seed)) + outfile.write(self.sgffilestart) + + if handicap > 1: + outfile.write("AB"); + for stone in self.handicap_stones: + outfile.write("[%s]" %(coords_to_sgf(size, stone))) + outfile.write("PL[W]\n") + + to_play = self.first_to_play + + for move in self.moves: + sgfmove = coords_to_sgf(size, move) + outfile.write(";%s[%s]\n" % (to_play, sgfmove)) + if to_play == "B": + to_play = "W" + else: + to_play = "B" + outfile.write(")\n") + outfile.close + + def set_handicap(self, handicap): + self.handicap = handicap + + def swap_players(self): + temp = self.whiteplayer + self.whiteplayer = self.blackplayer + self.blackplayer = temp + + def play(self, sgffile): + global verbose + global cursor + global use_sql + global sgf_to_png + + if verbose >= 1: + print "Setting boardsize and komi for black\n" + self.blackplayer.boardsize(self.size) + self.blackplayer.komi(self.komi) + + if verbose >= 1: + print "Setting boardsize and komi for white\n" + self.whiteplayer.boardsize(self.size) + self.whiteplayer.komi(self.komi) + + self.handicap_stones = [] + + if self.endgamefile == "": + if self.handicap < 2: + self.first_to_play = "B" + else: + self.handicap_stones = self.blackplayer.handicap(self.handicap, self.handicap_type) + for stone in self.handicap_stones: + self.whiteplayer.black(stone) + self.first_to_play = "W" + else: + self.blackplayer.loadsgf(self.endgamefile, self.endgame_start) + self.blackplayer.set_random_seed("0") + self.whiteplayer.loadsgf(self.endgamefile, self.endgame_start) + self.whiteplayer.set_random_seed("0") + if self.blackplayer.is_known_command("list_stones"): + self.get_position_from_engine(self.blackplayer) + elif self.whiteplayer.is_known_command("list_stones"): + self.get_position_from_engine(self.whiteplayer) + + to_play = self.first_to_play + + self.moves = [] + passes = 0 + won_by_resignation = "" + while passes < 2: + if to_play == "B": + move = self.blackplayer.genmove("black") + if move[:5] == "ERROR": + # FIXME: write_sgf + sys.exit(1) + + if move[:6] == "resign": + if verbose >= 1: + print "Black resigns" + won_by_resignation = "W+Resign" + break + else: + self.moves.append(move) + if string.lower(move[:4]) == "pass": + passes = passes + 1 + self.whiteplayer.black(move); + if verbose >= 1: + print "Black passes" + else: + passes = 0 + self.whiteplayer.black(move) + if verbose >= 1: + print "Black plays " + move + to_play = "W" + else: + move = self.whiteplayer.genmove("white") + if move[:5] == "ERROR": + # FIXME: write_sgf + sys.exit(1) + + if move[:6] == "resign": + if verbose >= 1: + print "White resigns" + won_by_resignation = "B+Resign" + break + else: + self.moves.append(move) + if string.lower(move[:4]) == "pass": + passes = passes + 1 + self.blackplayer.white(move) + if verbose >= 1: + print "White passes" + else: + passes = 0 + self.blackplayer.white(move) + if verbose >= 1: + print "White plays " + move + to_play = "B" + + if verbose >= 2: + print self.whiteplayer.showboard() + "\n" + + score_path= "/home/ai/ai.mindstab.net/htdocs/go/sgf/" + + file_prefix = str(int(time.time())) +'.'+ str(os.getpid()) + score_file = file_prefix+".sgf"; + image_file = file_prefix+".png"; + + self.writesgf(score_path + score_file) + + if won_by_resignation == "": + self.resultw = self.whiteplayer.final_score(score_path +score_file) + self.resultb = self.resultw #self.blackplayer.final_score() + else: + self.resultw = won_by_resignation; + self.resultb = won_by_resignation; + if self.whiteplayer.protocol_version[0:6] == "gtp2ip": + self.whiteplayer.send_score(self.resultw) + if self.blackplayer.protocol_version[0:6] == "gtp2ip": + self.blackplayer.send_score(self.resultw) + + winner = self.resultw[:1] + score = float((self.resultw[2:])) + if winner == "w" or winner == "W": + score = 0 - score + + if use_sql: + cursor.execute("INSERT INTO matchs (black_id, white_id, score, date, black_time, white_time, sgffile, handicap) values (%d, %d, %f, now(), \"%s\", \"%s\", \"%s\", %d);" % (self.blackplayer.bot_id, self.whiteplayer.bot_id, score, self.blackplayer.cputime(), self.whiteplayer.cputime(), score_file, 0) ) + os.popen(sgf_to_png + ' ' + score_path + score_file + ' ' + score_path + image_file).read(); + + + # if self.resultb == self.resultw: + # print "Result: ", self.resultw + # else: + # print "Result according to W: ", self.resultw + # print "Result according to B: ", self.resultb + # FIXME: $self->writesgf($sgffile) if defined $sgffile; + #if sgffile != "": + # print "writing " + sgffile + # self.writesgf(sgffile) + + def result(self): + return (self.resultw, self.resultb) + + def cputime(self): + cputime = {} + cputime["white"] = self.whiteplayer.cputime() + cputime["black"] = self.blackplayer.cputime() + return cputime + + def quit(self): + self.blackplayer.quit() + self.whiteplayer.quit() + + +class GTP_match: + + # Class members: + # black + # white + # size + # komi + # handicap + # handicap_type + + def __init__(self, whitecommand, blackcommand, size, komi, handicap, + handicap_type, streak_length, endgamefilelist): + self.white = whitecommand + self.black = blackcommand + self.size = size + self.komi = komi + self.handicap = handicap + self.handicap_type = handicap_type + self.streak_length = streak_length + self.endgamefilelist = endgamefilelist + + def endgame_contest(self, sgfbase): + results = [] + i = 1 + for endgamefile in self.endgamefilelist: + game1 = GTP_game(self.white, self.black, self.size, self.komi, + 0, "", endgamefile) + game2 = GTP_game(self.black, self.white, self.size, self.komi, + 0, "", endgamefile) + if verbose: + print "Replaying", endgamefile + print "Black:", self.black + print "White:", self.white + game1.play("") + result1 = game1.result()[0] + if result1 != "0": + plain_result1 = re.search(r"([BW]\+)([0-9]*\.[0-9]*)", result1) + result1_float = float(plain_result1.group(2)) + else: + plain_result1 = re.search(r"(0)", "0") + result1_float = 0.0 + if result1[0] == "B": + result1_float *= -1 + if verbose: + print "Result:", result1 + print "Replaying", endgamefile + print "Black:", self.white + print "White:", self.black + game2.play("") + result2 = game2.result()[1] + if verbose: + print "Result:", result2 + if result2 != "0": + plain_result2 = re.search(r"([BW]\+)([0-9]*\.[0-9]*)", result2) + result2_float = float(plain_result2.group(2)) + else: + plain_result2 = re.search(r"(0)", "0") + result2_float = 0.0 + + if result2[0] == "B": + result2_float *= -1 + results.append(result1_float - result2_float) + if (result1 != result2): + print endgamefile+ ":", plain_result1.group(), \ + plain_result2.group(), "Difference:", + print result1_float - result2_float + else: + print endgamefile+": Same result:", plain_result1.group() + sgffilename = "%s%03d" % (sgfbase, i) + print "writing SGFS" + game1.writesgf(sgffilename + "_1.sgf") + game2.writesgf(sgffilename + "_2.sgf") + game1.quit() + game2.quit() + i += 1 + return results + + def play(self, games, sgfbase): + last_color = "" + last_streak = 0 + game = GTP_game(self.white, self.black, + self.size, self.komi, self.handicap, + self.handicap_type, "") + results = [] + for i in range(games): + if sgfbase != "": + sgffilename = "%s%03d.sgf" % (sgfbase, i + 1) + else: + sgffilename = "" + game.play(sgffilename) + result = game.result() + if result[0] == result[1]: + print "Game %d: %s" % (i + 1, result[0]) + else: + print "Game %d: %s %s" % (i + 1, result[0], result[1]) + + if result[0][0] == last_color: + last_streak += 1 + elif result[0][0] != "0": + last_color = result[0][0] + last_streak = 1 + + if last_streak == self.streak_length: + if last_color == "W": + self.handicap += 1 + if self.handicap == 1: + self.handicap = 2 + print "White wins too often. Increasing handicap to %d" \ + % (self.handicap) + else: + if self.handicap > 0: + self.handicap -= 1 + if self.handicap == 1: + self.handicap = 0 + print "Black wins too often. Decreasing handicap to %d" \ + % (self.handicap) + else: + self.handicap = 2 + game.swap_players() + print "Black looks stronger than white. Swapping colors and setting handicap to 2" + game.set_handicap(self.handicap) + last_color = "" + last_streak = 0 + results.append(result) + cputime = game.cputime() + game.quit() + return results, cputime + + +# ================================================================ +# Main program +# + + +# Default values +# + +white = "" +black = "" +port = 0 +komi = "" +size = "19" +handicap = 0 +handicap_type = "fixed" +streak_length = -1 +endgame_start_at = 0 + +games = 1 +sgfbase = "" + +verbose = 0 + +helpstring = """ + +Run with: + +gtpserver.py --port [twogtp options] + +Possible twogtp options: + + --verbose 1 (to list moves) or --verbose 2 (to draw board) + --komi + --handicap + --free-handicap + --adjust-handicap (change handicap by 1 after wins + in a row) + --size (default 19) + --games (default 1) + --sgfbase (create sgf files with sgfbase as basename) + --endgame (endgame contest - add filenames of + games to be replayed after last option) +""" + +def usage(): + print helpstring + sys.exit(1) + +#print sys.argv + +try: + (opts, params) = getopt(sys.argv[1:], "", + ["port=", + "black=", + "white=", + "verbose=", + "komi=", + "boardsize=", + "size=", + "handicap=", + "free-handicap=", + "adjust-handicap=", + "games=", + "sgfbase=", + "endgame=", + ]) +except: + usage() + +for opt, value in opts: + if opt == "--black": + black = value + elif opt == "--white": + white = value + elif opt == "--port": + port = value + elif opt == "--verbose": + verbose = int(value) + elif opt == "--komi": + komi = value + elif opt == "--boardsize" or opt == "--size": + size = value + elif opt == "--handicap": + handicap = int(value) + handicap_type = "fixed" + elif opt == "--free-handicap": + handicap = int(value) + handicap_type = "free" + elif opt == "--adjust-handicap": + streak_length = int(value) + elif opt == "--games": + games = int(value) + elif opt == "--sgfbase": + sgfbase = value + elif opt == "--endgame": + endgame_start_at = int(value) + +if endgame_start_at != 0: + endgame_filelist = params +else: + endgame_filelist = [] + if params != []: + usage() + + +if port == 0: #black == "" or white == "": + usage() + +if komi == "": + if handicap > 1 and streak_length == -1: + komi = "0.5" + else: + komi = "5.5" + + +serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +serversocket.bind( ('', int(port))) +serversocket.settimeout(30) +serversocket.listen(2) +try: + print "%s waiting for gtpclient 1 on port %d" % (os.getpid(), int(port)) + (whitesocket, whiteaddress) = serversocket.accept() + print "gtpclient 1 connected from %s!" % (str(whiteaddress)) +except socket.timeout: + print "%s Timeout waiting for client 1, exiting..." % (os.getpid()) + serversocket.close() + sys.exit(-1) + +whitenp = netpipe.netpipe(whitesocket) +try: + print "%s waiting for gtpclient 2 on port %d" % (os.getpid(), int(port)) + (blacksocket, blackaddress) = serversocket.accept() + print "gtpclient 2 connected from %s!" % (str(blackaddress)) +except socket.timeout: + print "%s Timeout waiting for client 2, exiting..." % (os.getpid()) + whitenp.send("quit") + whitenp.close() + serversocket.close() + sys.exit(-1) + +print "%s playing match!" % (os.getpid()) + +blacknp = netpipe.netpipe(blacksocket) + +match = GTP_match(whitenp, blacknp, size, komi, handicap, handicap_type, + streak_length, endgame_filelist) +if endgame_filelist != []: + results = match.endgame_contest(sgfbase) + win_black = 0 + win_white = 0 + for res in results: + print res + if res > 0.0: + win_white += 1 + elif res < 0.0: + win_black += 1 + print "%d games, %d wins for black, %d wins for white." \ + % (len(results), win_black, win_white) + +else: + results, cputimes = match.play(games, sgfbase) + + i = 0 + for resw, resb in results: + i = i + 1 + if resw == resb: + print "Game %d: %s" % (i, resw) + else: + print "Game %d: %s %s" % (i, resb, resw) + if (cputimes["white"] != "0"): + print "White: %ss CPU time" % cputimes["white"] + if (cputimes["black"] != "0"): + print "Black: %ss CPU time" % cputimes["black"] + + +whitenp.close() +blacknp.close() +serversocket.close() +if use_sql: + cursor.close () + conn.close () + diff --git a/netpipe.py b/netpipe.py new file mode 100644 index 0000000..0cb9de6 --- /dev/null +++ b/netpipe.py @@ -0,0 +1,38 @@ +import socket + +class netpipe: + '''NetPipe: Sends data from one place to another''' + + def __init__(self, sock=None): + if sock is None: + self.sock = socket.socket( + socket.AF_INET, socket.SOCK_STREAM) + else: + self.sock = sock + + def connect(self, host, port): + self.sock.connect((host, port)) + + def send(self, msg): + msg = ("%04d" % len(msg)) + msg + #print "np->: "+msg + totalsent = 0 + while totalsent < len(msg): + sent = self.sock.send(msg[totalsent:]) + if sent == 0: + raise SocketClosed, "socket connection broken" + totalsent = totalsent + sent + + def receive(self): + length = int(self.sock.recv(4)) + msg = '' + while len(msg) < length: + line = self.sock.recv(length - len(msg)) + if line == '': + raise SocketClosed, "socket connection broken" + msg = msg + line + #print "np<-: " + msg + return msg + + def close(self): + self.sock.close() diff --git a/website/index.php b/website/index.php new file mode 100644 index 0000000..42d8876 --- /dev/null +++ b/website/index.php @@ -0,0 +1,304 @@ + 0) +// $stats[ $bots[ $row['black_id'] ][ 'name' ] ][ 'wins' ]++; +// else if ($row['score'] < 0) +// $stats[ $bots[ $row['white_id'] ][ 'name' ] ][ 'wins' ]++; + +} + + +$statsql = "SELECT black_id, white_id, round(avg(score),1) as 'avg', sum(score) as 'sum', count(*) as 'games' FROM matchs group by black_id, white_id;"; +$res = mysql_query($statsql); +while ($row = mysql_fetch_array($res)) { + $stats[] = $row; + $avg[$row['black_id']][$row['white_id']]['total'] += $row['sum']; + $avg[$row['black_id']][$row['white_id']]['games'] += $row['games']; + $avg[$row['black_id']][$row['white_id']]['avg'] = round($avg[$row['black_id']][$row['white_id']]['total']/$avg[$row['black_id']][$row['white_id']]['games'],2); + if ($row['black_id'] != $row['white_id']) { + $avg[$row['white_id']][$row['black_id']]['total'] += -$row['sum']; + $avg[$row['white_id']][$row['black_id']]['games'] += $row['games']; + $avg[$row['white_id']][$row['black_id']]['avg'] = round($avg[$row['white_id']][$row['black_id']]['total']/$avg[$row['white_id']][$row['black_id']]['games'],2); + } +} + +function format_time($time_str) +{ + if ($time_str == "0" or $time_str == null) + return "?"; + else + return round($time_str, 2)."s"; +} + + +?> + + + + + + + + + ai.mindstab.net: Go competition + + + + + + + + + +
+

Return to Wiki

+All Matchs
"; +print "

$botname $botversion

"; +print "Wiki entry for $botname

"; +}/* +$bot = $stats[$botname]; +print ""; +print ""; +print ""; + +print "
WinsPercent
".(isset($bot['wins']) ? $bot['wins'] : '0')."/".$bot['matchs'] . "".round($bot['wins']/$bot['matchs']*100)."%
"; + + +} else { +print "

Bot Stats

"; +print ""; +foreach ($stats as $name => $bot) { +print ""; +print ""; +$botname=""; +} +print "
BotWinsPercent
".$name."".(isset($bot['wins']) ? $bot['wins'] : '0')."/".$bot['matchs'] . "".round($bot['wins']/$bot['matchs']*100)."%
"; +}*/ + +//print "
";
+//print_r($avg);
+//print "

"; + +print "

Agerage Scores

"; //These stats wont make sense if handicapping is added +print ""; +foreach ($bots as $bot) { + if ($bot['version'] == $botversion || (!isset($botversion) && $bot['name'] == $botname)) + { + print ""; +} +foreach ($bots as $black) { + print ""; + if ($black['version'] == $botversion || (!isset($botversion) && $black['name'] == $botname)) + { + print ""; + foreach ($bots as $white) { + if ($white['version'] == $botversion || $black['version'] == $botversion || (!isset($botversion) && ($white['name'] == $botname || $black['name'] == $botname))) + { + print "\n"; + } + print "\n"; +} + +print ""; + +print "
Black \ White"; + } else if (!isset($botname)) { + print ""; + } else { + print ""; + } + + print "".$bot['name'].""; + print " - ".$bot['version']."
"; + } else if (!isset($botname)) { + print ""; + } else { + print ""; + } + print "".$black['name'].""; + print " - ".$black['version']."= 0) { + $score = "B+$score"; + if ($black['name'] == $botname) + $score = "".$score.""; + } else { + $score="W+".abs($score); + if ($white['name'] == $botname) + $score = "".$score.""; + } + if (substr($score, 0, 1) == "W") + print " white\">".$score; + else + print " black\">".$score; + } else { + print " empty\">"; + } + print "
"; + +/* +print ""; +foreach ($stats as $stat) { + print ""; + print ""; + print ""; + print ""; + $score = $stat['avg']; + if ($score >= 0) { + $score = "B+$score"; + + print ""; + } else { + $score="W+".abs($score); + print ""; + } + print ""; +} +print "
BlackWhiteGames PlayedAverage Score
".$bots[$stat['black_id']]['name'] . ""; + print " - " .$bots[$stat['black_id']]['version'] ."".$bots[$stat['white_id']]['name'] . ""; + print " - " .$bots[$stat['white_id']]['version'] ."".$stat['games']."".$score."".$score."
"; +*/ + +?> + +

Matchs

+ + +"; + print ""; + print ""; + $score = $match['score']; + if ($score >= 0) { + $score = "B+$score"; + if ($bots[$match['black_id']]['name'] == $botname) + $score = "$score"; + } else { + $score="W+".abs($score); + if ($bots[$match['white_id']]['name'] == $botname) + $score = "$score"; + } + + + print ""; + print ""; + print ""; + print ""; + print ""; + print ""; + print ""; +} +print "
BlackWhiteScoreDateBlack TimeWhite TimeHandicapSGF file
".$bots[$match['black_id']]['name'] . ""; + print " - " .$bots[$match['black_id']]['version'] ."".$bots[$match['white_id']]['name'] . ""; + print " - " .$bots[$match['white_id']]['version'] ."".$score."" . $match['date']."". format_time($match['black_time'], 2)."". format_time($match['white_time'], 2)."".$match['handicap']."\"SGF
"; +if (isset($_GET['name'])) +{ + $args = "name=".$_GET['name']."&"; +} else if (isset($_GET['version'])) { + $args = "version=".$_Get['version']."&"; +} +if (isset($_GET['page'])) +{ + print "
Prev"; + print " - Next"; +} else { + print "Next"; +} +?> +
+ +