├── report ├── 1.png ├── 2.png ├── 3.png ├── 4.png ├── ai3.png ├── logo.jpg ├── cover.pdf ├── report.pdf ├── acl17-latex │ ├── acl2017.pdf │ ├── acl2017.bib │ └── acl2017.sty ├── ref.bib ├── cover.tex ├── acl2017.sty └── report.tex ├── app ├── __init__.py ├── static │ ├── img │ │ └── board.jpg │ ├── css │ │ └── main.css │ └── js │ │ ├── main.js │ │ └── jquery-weui.js ├── views.py ├── templates │ └── index.html ├── gomoku_web.py └── gomoku.py ├── run.py ├── game_record └── records.npy ├── .gitignore ├── runp.py ├── .gitattributes ├── AI0.py ├── small_gomoku.py ├── LICENSE ├── README.md ├── test.py ├── find_match_record.py ├── genetic_algo ├── gomoku_web_genetic.py ├── train_genetic.ipynb ├── genetic_AI.py └── gomoku.py ├── AI1.py ├── AI6.py ├── AI2.py ├── clean_game_records.ipynb ├── AI5.py └── AI3.py /report/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TianxiaoHu/GomokuAgent/HEAD/report/1.png -------------------------------------------------------------------------------- /report/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TianxiaoHu/GomokuAgent/HEAD/report/2.png -------------------------------------------------------------------------------- /report/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TianxiaoHu/GomokuAgent/HEAD/report/3.png -------------------------------------------------------------------------------- /report/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TianxiaoHu/GomokuAgent/HEAD/report/4.png -------------------------------------------------------------------------------- /report/ai3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TianxiaoHu/GomokuAgent/HEAD/report/ai3.png -------------------------------------------------------------------------------- /report/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TianxiaoHu/GomokuAgent/HEAD/report/logo.jpg -------------------------------------------------------------------------------- /report/cover.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TianxiaoHu/GomokuAgent/HEAD/report/cover.pdf -------------------------------------------------------------------------------- /report/report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TianxiaoHu/GomokuAgent/HEAD/report/report.pdf -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | 3 | app = Flask(__name__) 4 | from app import views 5 | -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from app import app 4 | app.run(debug=True, threaded=True) 5 | -------------------------------------------------------------------------------- /app/static/img/board.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TianxiaoHu/GomokuAgent/HEAD/app/static/img/board.jpg -------------------------------------------------------------------------------- /game_record/records.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TianxiaoHu/GomokuAgent/HEAD/game_record/records.npy -------------------------------------------------------------------------------- /report/acl17-latex/acl2017.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TianxiaoHu/GomokuAgent/HEAD/report/acl17-latex/acl2017.pdf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .idea/ 3 | *.aux 4 | *.bbl 5 | *.blg 6 | *.log 7 | *.out 8 | *.synctex.gz 9 | *.sgf 10 | .ipynb_checkpoints/ 11 | -------------------------------------------------------------------------------- /runp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import qrcode 3 | import PIL.Image 4 | import socket 5 | localIP = socket.gethostbyname(socket.gethostname()) 6 | port = 5000 7 | img = qrcode.make('http://' + localIP + ':' + str(port)) 8 | img.save("temp_qrcode.png") 9 | qr_code = PIL.Image.open("temp_qrcode.png") 10 | qr_code.show() 11 | 12 | from app import app 13 | app.run('0.0.0.0', port=port, debug=False, threaded=True) 14 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /AI0.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | def strategy(state): 4 | """ Information provided to you: 5 | state = (board, last_move, playing, board_size) 6 | board = (x_stones, o_stones) 7 | stones is a set contains positions of one player's stones. e.g. 8 | x_stones = {(8,8), (8,9), (8,10), (8,11)} 9 | playing = 0|1, the current player's index 10 | 11 | Your strategy will return a position code for the next stone, e.g. (8,7) 12 | """ 13 | board, last_move, playing, board_size = state 14 | x, y = last_move 15 | return (x + 1, y + 1) 16 | 17 | def finish(): 18 | pass 19 | -------------------------------------------------------------------------------- /report/ref.bib: -------------------------------------------------------------------------------- 1 | %% This BibTeX bibliography file was created using BibDesk. 2 | %% http://bibdesk.sourceforge.net/ 3 | 4 | %% Created for 胡天晓 at 2017-06-28 22:33:25 +0800 5 | 6 | 7 | %% Saved with string encoding Unicode (UTF-8) 8 | 9 | 10 | 11 | @book{ml, 12 | Author = {Christopher M. Bishop}, 13 | Date-Added = {2017-06-28 14:31:10 +0000}, 14 | Date-Modified = {2017-06-28 14:33:24 +0000}, 15 | Publisher = {Springer}, 16 | Title = {Pattern Recognition and Machine Learning}, 17 | Year = {2007}} 18 | 19 | @book{ai, 20 | Author = {Stuart Russell and Peter Norvig}, 21 | Date-Modified = {2017-06-28 14:31:07 +0000}, 22 | Publisher = {Prentice Hall}, 23 | Title = {Artificial Intelligence: A Modern Approach}, 24 | Year = {2009}} 25 | -------------------------------------------------------------------------------- /small_gomoku.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -- coding: utf-8 -- 3 | from app.gomoku_web import * 4 | players = ['YOU', 'AI0'] 5 | game = Gomoku_Web(board_size=7, winning_num=4) 6 | tmp = [] 7 | for player in players: 8 | if player.startswith('AI'): 9 | p = Player(player, ai_script=player, level=5) 10 | else: 11 | p = Player(player) 12 | tmp.append(p) 13 | game.players = tmp 14 | 15 | center = ((game.board_size+1) / 2, (game.board_size+1) / 2) 16 | next_action, winner = game.web_play(center) 17 | while not winner: 18 | if game.players[game.playing].is_ai: 19 | next_action, winner = game.web_play(next_action) 20 | else: 21 | stone = raw_input("Enter your stone like '4 5'...") 22 | stone = map(lambda x: int(x), stone.split(' ')) 23 | next_action, winner = game.web_play(tuple(stone)) 24 | print winner 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 GomokuAgent 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GomokuAgent 2 | 3 | A web version of the gomoku game with AI. 4 | 5 | This is the final project for Artificial Intelligence(DATA130008.01)@ Fudan University. 6 | 7 | Built together with[@Anthony-Xu](https://github.com/Anthony-Xu) and [@iriserika](https://github.com/iriserika) . 8 | 9 | - Play Gomoku on PC 10 | 11 | First run the game server, enter command: ```python run.py``` 12 | 13 | Open the website http://127.0.0.1:5000/ in your browser. 14 | 15 | - Play Gomoku on Mobile 16 | 17 | Please make sure your server and mobile are connected to the same WLAN. 18 | 19 | First run the game server, enter command: ```python runp.py``` 20 | 21 | Scan the QR code appearing on the screen using *Wechat*. 22 | 23 | **Happy Gomoku!** :-) 24 | 25 | ## Current AIs 26 | 27 | - AI0: just for a test 28 | - AI1: greedy algorithm & evaluation function ver 1 29 | - AI2: 2-layer naive a-b prunning 30 | - AI3: MCTS 31 | - AI4: 8-layer speed-optimized version of a-b prunning 32 | - AI5: greedy algorithm & evaluation function ver 2(improved) 33 | - AI6: greedy algorithm & evaluation function ver 1, also can find a game with same opening and place a stone(from 5570 game history) 34 | -------------------------------------------------------------------------------- /app/views.py: -------------------------------------------------------------------------------- 1 | from flask import render_template, request, jsonify 2 | from app import app 3 | import gomoku_web 4 | 5 | @app.route('/') 6 | @app.route('/index') 7 | def index(): 8 | return render_template('index.html') 9 | 10 | @app.route('/_start', methods=['GET']) 11 | def start(): 12 | game.reset() 13 | p1 = request.args.get('p1', 'You', type=str) 14 | p2 = request.args.get('p2', 'AI1', type=str) 15 | lv = request.args.get('lv', 5, type=int) 16 | game.players = [] 17 | for player_name in [p1,p2]: 18 | if player_name.startswith('AI'): 19 | p = gomoku_web.Player(player_name,ai_script=player_name,level=lv) 20 | else: 21 | p = gomoku_web.Player(player_name) 22 | game.players.append(p) 23 | game.print_board() 24 | return 'Success' 25 | 26 | @app.route('/_player_set', methods=['GET']) 27 | def player_set(): 28 | position = request.args.get('position','') 29 | stone = tuple(int(i) for i in position.split(',')) 30 | action = (stone[0]+1, stone[1]+1) # we start from 1 in the game engine 31 | next_action, winner = game.web_play(action) 32 | if isinstance(next_action, tuple): 33 | stone = (next_action[0]-1, next_action[1]-1) 34 | else: 35 | stone = None 36 | return jsonify(next_move=stone, winner=winner) 37 | 38 | @app.route('/_reset', methods=['GET']) 39 | def reset(): 40 | game.reset() 41 | return 'Success' 42 | 43 | @app.route('/_undo', methods=['GET']) 44 | def undo(): 45 | game.undo() 46 | return 'Success' 47 | 48 | game = gomoku_web.Gomoku_Web(board_size=15) 49 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -- coding: utf-8 -- 3 | from app.gomoku_web import * 4 | import argparse 5 | import numpy as np 6 | 7 | parser = argparse.ArgumentParser() 8 | parser.add_argument("offensive_AI", help="offensive position agent script") 9 | parser.add_argument("defensive_AI", help="defensive position agent script") 10 | parser.add_argument("size", help="chess board size") 11 | parser.add_argument("filepath", help="print filepath") 12 | parser.add_argument("times", help="pay times") 13 | parser.add_argument("silent", help="choose if to print board step by step") 14 | 15 | args = parser.parse_args() 16 | offensive_AI = args.offensive_AI 17 | defensive_AI = args.defensive_AI 18 | board_size = int(args.size) 19 | filepath = args.filepath 20 | times = int(args.times) 21 | players = [offensive_AI, defensive_AI] 22 | silent_mode = True if args.silent == 's' else False 23 | 24 | if __name__ == '__main__': 25 | f = open(filepath, 'w') 26 | f.write(offensive_AI + " vs. " + defensive_AI + "\n") 27 | result = [] 28 | for i in range(times): 29 | game = Gomoku_Web(board_size=board_size, players=players, silent_mode=silent_mode) 30 | tmp = [] 31 | for player in players: 32 | if player.startswith('AI'): 33 | p = Player(player, ai_script=player, level=5) 34 | else: 35 | p = Player(player) 36 | tmp.append(p) 37 | game.players = tmp 38 | f.write("Round " + str(i + 1) + '\n') 39 | print "Round", i+1, "is playing..." 40 | center = ((board_size+1) / 2, (board_size+1) / 2) 41 | next_action, winner = game.web_play(center) 42 | while not winner: 43 | next_action, winner = game.web_play(next_action) 44 | game.print_board() 45 | print winner 46 | result.append(winner) 47 | f.write("winner: " + winner + '\n') 48 | result = np.array(result) 49 | for res in np.unique(result): 50 | print res, len(result[result==res]) 51 | f.write(res + ' ' + str(len(result[result==res])) + '\n') 52 | f.close() 53 | -------------------------------------------------------------------------------- /report/cover.tex: -------------------------------------------------------------------------------- 1 | \documentclass[12pt, oneside,a4paper]{article} 2 | \usepackage{geometry} 3 | \usepackage{indentfirst} 4 | \usepackage{listings} 5 | \usepackage{color} 6 | \usepackage{textcomp} 7 | \definecolor{dkgreen}{rgb}{0,0.6,0} 8 | \definecolor{gray}{rgb}{0.5,0.5,0.5} 9 | \definecolor{mauve}{rgb}{0.58,0,0.82} 10 | \usepackage{amssymb,amsmath} 11 | \usepackage[noend]{algpseudocode} 12 | \usepackage{algorithmicx,algorithm} 13 | \usepackage{graphicx} 14 | \usepackage{multirow} 15 | \usepackage{float} 16 | \usepackage{fancyhdr} 17 | \lstset{frame=tb, 18 | language=Python, 19 | aboveskip=3mm, 20 | belowskip=3mm, 21 | showstringspaces=false, 22 | columns=flexible, 23 | basicstyle={\small\ttfamily}, 24 | numbers=none, 25 | numberstyle=\tiny\color{gray}, 26 | keywordstyle=\color{blue}, 27 | commentstyle=\color{dkgreen}, 28 | stringstyle=\color{mauve}, 29 | breaklines=true, 30 | breakatwhitespace=true, 31 | tabsize=3 32 | } 33 | \geometry{a4paper,left=2cm,right=2cm,top=3cm,bottom=3cm} 34 | 35 | \pagestyle{fancy} 36 | \lhead{Artificial Intelligence Project \# 1} 37 | \rhead{Hu Tianxiao, Xu Hui, Zhang Bing} 38 | 39 | \newcommand{\HRule}{\rule{\linewidth}{0.5mm}} 40 | \begin{document} 41 | 42 | \begin{titlepage} 43 | \begin{center} 44 | % Upper part of the page 45 | \textsc{\LARGE Fudan University}\\[1.5cm] 46 | \textsc{\Large Introduction to Artificial Intelligence}\\[0.5cm] 47 | % Title 48 | \HRule \\[0.6cm] 49 | { \huge \bfseries Final Project: }\\[0.4cm] 50 | { \huge \bfseries Smart Gomoku Agent}\\[0.4cm] 51 | 52 | \HRule \\[1.5cm] 53 | \includegraphics[width=2in]{logo.jpg}\\[1cm] 54 | 55 | % Author 56 | \begin{minipage}{0.4\textwidth} 57 | \begin{flushleft} \large 58 | 59 | 60 | \begin{center} 61 | \vspace{0.5cm} 62 | \textbf{Group Members:}\\ 63 | \vspace{0.4cm} 64 | Tianxiao Hu 14300240007\\ 65 | \vspace{0.4cm} 66 | Hui Xu \hspace*{0.8cm} 14300180059\\ 67 | \vspace{0.4cm} 68 | Bing Zhang 14307130338 69 | \end{center} 70 | \end{flushleft} 71 | \end{minipage} 72 | \vfill 73 | % Bottom of the page 74 | {\large \today} 75 | \end{center} 76 | \end{titlepage} 77 | 78 | 79 | 80 | \end{document} -------------------------------------------------------------------------------- /app/static/css/main.css: -------------------------------------------------------------------------------- 1 | /* Copied and Modified from opensource project https://github.com/lihongxun945/gobang */ 2 | 3 | html { 4 | font-size: 10px; 5 | } 6 | 7 | * { 8 | -webkit-touch-callout: none; 9 | -webkit-user-select: none; 10 | -khtml-user-select: none; 11 | -moz-user-select: none; 12 | -ms-user-select: none; 13 | user-select: none; 14 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 15 | } 16 | 17 | @media (min-width: 370px) { 18 | html { 19 | font-size: 11px; 20 | } 21 | } 22 | @media (min-width: 400px) { 23 | html { 24 | font-size: 12px; 25 | } 26 | } 27 | @media (min-width: 500px) { 28 | html { 29 | font-size: 14px; 30 | } 31 | } 32 | h1 { 33 | color: #3cc51f; 34 | font-size: 2rem; 35 | margin: 1rem; 36 | text-align: center; 37 | } 38 | p { 39 | font-size: 1.4rem; 40 | color: #999; 41 | text-align: center; 42 | } 43 | .board { 44 | position: relative; 45 | background: url("../img/board.jpg"); 46 | width: 32rem; 47 | height: 32rem; 48 | margin: 1.5rem auto; 49 | background-size: 100%; 50 | } 51 | .board .chessman { 52 | height: 1.7rem; 53 | width: 1.7rem; 54 | margin-left: -0.85rem; 55 | margin-top: -0.85rem; 56 | border-radius: .85rem; 57 | background: white; 58 | position: absolute; 59 | cursor: no-drop; 60 | user-select: none; 61 | } 62 | .board .chessman.black { 63 | background: black; 64 | } 65 | .board .indicator { 66 | box-sizing: border-box; 67 | height: 2rem; 68 | width: 2rem; 69 | margin-left: -1rem; 70 | margin-top: -1rem; 71 | border: 1px solid #EF4F4F; 72 | position: absolute; 73 | } 74 | .buttons { 75 | width: 30rem; 76 | margin: auto; 77 | padding-bottom: 1rem; 78 | } 79 | .status { 80 | text-align: center; 81 | margin: 1rem; 82 | font-size: 1.4rem; 83 | height: 2rem; 84 | line-height: 2rem; 85 | color: #666; 86 | } 87 | 88 | @media screen and (min-width: 1024px) { 89 | .weui_dialog, 90 | .weui_toast { 91 | left: 32.5%; 92 | } 93 | } 94 | footer { 95 | margin: 1rem; 96 | text-align: center; 97 | color: gray; 98 | } 99 | 100 | input { 101 | width: 50px; 102 | user-select: auto; 103 | -webkit-user-select: auto; 104 | } 105 | 106 | -------------------------------------------------------------------------------- /find_match_record.py: -------------------------------------------------------------------------------- 1 | # -- coding: utf-8 -- 2 | import numpy as np 3 | import random 4 | import os 5 | 6 | basedir = os.path.abspath(os.path.dirname(__file__)) 7 | record = np.load(os.path.join(basedir, 'game_record', 'records.npy')) 8 | record = map(lambda x: x.strip(), record) 9 | 10 | def change_format(game_record): 11 | return game_record.split(';') 12 | 13 | record = map(change_format, record) 14 | record = np.array(record) 15 | 16 | alphabet = 'abcdefghijklmno' 17 | alpha_dic = dict(zip(alphabet, np.arange(1, 15))) 18 | 19 | def match_record(record, stone_num, board): 20 | try: 21 | stones = record[:stone_num] 22 | white = [stones[i] for i in range(stone_num) if i % 2 == 1] 23 | black = [stones[i] for i in range(stone_num) if i % 2 == 0] 24 | white = map(lambda x: x[2:-1], white) 25 | black = map(lambda x: x[2:-1], black) 26 | white = set([(alpha_dic[i[0]], alpha_dic[i[1]]) for i in white]) 27 | black = set([(alpha_dic[i[0]], alpha_dic[i[1]]) for i in black]) 28 | record_board = (black, white) 29 | if board == record_board: 30 | return True 31 | else: 32 | return False 33 | except: 34 | return False 35 | 36 | state = ((set([(8, 8)]), set([(9, 9)])), (8, 8), 2, 15) 37 | 38 | def find_match_record(state): 39 | """ Information provided to you: 40 | state = (board, last_move, playing, board_size) 41 | board = (x_stones, o_stones) 42 | stones is a set contains positions of one player's stones. e.g. 43 | x_stones = {(8,8), (8,9), (8,10), (8,11)} 44 | playing = 0|1, the current player's index 45 | This strategy will search 5570 games and return a matched next step, e.g. (8,7) 46 | """ 47 | board, last_move, playing, board_size = state 48 | x, y = last_move 49 | turns = len(board[0]) + len(board[1]) 50 | match_game = record[map(lambda x: match_record(x, turns, board), record)] 51 | if len(match_game): 52 | print len(match_game), 'game matched!' 53 | select_game = random.choice(match_game) 54 | next_place = select_game[turns] 55 | x, y = alpha_dic[next_place[2]], alpha_dic[next_place[3]] 56 | return (x, y) 57 | else: 58 | print 'No game matched!' 59 | return None 60 | 61 | print find_match_record(state) 62 | -------------------------------------------------------------------------------- /report/acl17-latex/acl2017.bib: -------------------------------------------------------------------------------- 1 | @book{Aho:72, 2 | author = {Alfred V. Aho and Jeffrey D. Ullman}, 3 | title = {The Theory of Parsing, Translation and Compiling}, 4 | year = "1972", 5 | volume = "1", 6 | publisher = {Prentice-Hall}, 7 | address = {Englewood Cliffs, NJ} 8 | } 9 | 10 | @book{APA:83, 11 | author = {{American Psychological Association}}, 12 | title = {Publications Manual}, 13 | year = "1983", 14 | publisher = {American Psychological Association}, 15 | address = {Washington, DC} 16 | } 17 | 18 | @article{ACM:83, 19 | author = {Association for Computing Machinery}, 20 | year = "1983", 21 | journal = {Computing Reviews}, 22 | volume = "24", 23 | number = "11", 24 | pages = "503--512" 25 | } 26 | 27 | @article{Chandra:81, 28 | author = {Ashok K. Chandra and Dexter C. Kozen and Larry J. Stockmeyer}, 29 | year = "1981", 30 | title = {Alternation}, 31 | journal = {Journal of the Association for Computing Machinery}, 32 | volume = "28", 33 | number = "1", 34 | pages = "114--133", 35 | doi = "10.1145/322234.322243", 36 | } 37 | 38 | @book{Gusfield:97, 39 | author = {Dan Gusfield}, 40 | title = {Algorithms on Strings, Trees and Sequences}, 41 | year = "1997", 42 | publisher = {Cambridge University Press}, 43 | address = {Cambridge, UK} 44 | } 45 | 46 | @InProceedings{P16-1001, 47 | author = "Goodman, James 48 | and Vlachos, Andreas 49 | and Naradowsky, Jason", 50 | title = "Noise reduction and targeted exploration in imitation learning for Abstract Meaning Representation parsing ", 51 | booktitle = "Proceedings of the 54th Annual Meeting of the Association for Computational Linguistics (Volume 1: Long Papers) ", 52 | year = "2016", 53 | publisher = "Association for Computational Linguistics", 54 | pages = "1--11", 55 | location = "Berlin, Germany", 56 | doi = "10.18653/v1/P16-1001", 57 | url = "http://aclweb.org/anthology/P16-1001" 58 | } 59 | 60 | @InProceedings{C14-1001, 61 | author = "Harper, Mary", 62 | title = "Learning from 26 Languages: Program Management and Science in the Babel Program", 63 | booktitle = "Proceedings of COLING 2014, the 25th International Conference on Computational Linguistics: Technical Papers", 64 | year = "2014", 65 | publisher = "Dublin City University and Association for Computational Linguistics", 66 | pages = "1", 67 | location = "Dublin, Ireland", 68 | url = "http://aclweb.org/anthology/C14-1001" 69 | } 70 | -------------------------------------------------------------------------------- /app/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Welcome to Gomoku Agent! 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |

Gomoku Agent

17 |

18 | Final project for  19 | Artificial Intelligence 20 | (DATA130008.01) @Fudan University. 21 |

22 |

23 | Black: 24 | 35 | White: 36 | 47 |

48 |
49 |
50 |
51 |
52 | 53 |
54 |
55 | 56 |
57 |
58 |
59 | Start 60 |
61 |
62 | Resign 63 |
64 |
65 | Undo 66 |
67 |
68 |
69 | 70 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /app/gomoku_web.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/env python 2 | import gomoku 3 | 4 | class Gomoku_Web(gomoku.Gomoku): 5 | """ Gomoku Game Rules: 6 | Two players alternatively put their stone on the board. First one got five in a row wins. 7 | """ 8 | def __init__(self, board_size=15, players=None, silent_mode=False, winning_num=5): 9 | self.reset() 10 | self.board_size = board_size 11 | self.fastmode = None 12 | self.playing = None 13 | self.silent_mode = silent_mode 14 | if players: 15 | self.players = [Player(player_name) for player_name in players] 16 | else: 17 | self.players = [] 18 | self.last_move = None 19 | self.first_center = None 20 | self.winning_num = winning_num 21 | 22 | def reset(self): 23 | self.board = (set(),set()) 24 | self.i_turn = 0 25 | self.hist_moves = [] 26 | self.winning_stones = set() 27 | 28 | def web_play(self, action): 29 | """ Controling the game play by the web function call""" 30 | self.playing = self.i_turn % 2 31 | current_player = self.players[self.playing] 32 | if action == (0, 0): 33 | print("Player %s admit defeat!" % current_player.name) 34 | self.last_move = action 35 | if self.place_stone() is False: 36 | return 37 | self.hist_moves.append(self.last_move) # for undo 38 | winner = self.check_winner() 39 | if not self.silent_mode: 40 | self.print_board() 41 | if winner is not None: 42 | print("########## %s is the WINNER! #########" % current_player.name) 43 | return None, winner 44 | self.i_turn += 1 45 | if self.i_turn == self.board_size ** 2: 46 | print("This game is a Draw!") 47 | return None, "Draw" 48 | 49 | next_action = self.check_next_ai() 50 | return next_action, winner 51 | 52 | def check_next_ai(self): 53 | """ check if the next move is AI """ 54 | self.playing = self.i_turn % 2 55 | current_player = self.players[self.playing] 56 | if current_player.is_ai: 57 | action = current_player.strategy(self.state) 58 | return action 59 | return None 60 | 61 | def undo(self): 62 | """ Undo opponent's last move and my last move """ 63 | if len(self.hist_moves) == 0: return 64 | opponent = int(not self.playing) 65 | opponent_last_move = self.hist_moves.pop() 66 | self.board[opponent].remove(opponent_last_move) 67 | if len(self.hist_moves) == 0: return 68 | my_last_move = self.hist_moves.pop() 69 | self.board[self.playing].remove(my_last_move) 70 | print("Undo!") 71 | if not self.silent_mode: 72 | self.print_board() 73 | 74 | 75 | class Player(gomoku.Player): 76 | def __init__(self, name, ai_script=None, level=None): 77 | self.name = name 78 | # search for the strategy file 79 | if ai_script is not None: 80 | print('Loading AI script %s'%ai_script) 81 | p = __import__(ai_script) 82 | try: 83 | self.strategy = p.strategy 84 | except: 85 | raise RuntimeError("Function strategy(state) is not found in %s"%ai_script) 86 | try: 87 | self.finish = p.finish 88 | except: 89 | pass 90 | self.is_ai = True 91 | if level: 92 | p.estimate_level = level 93 | else: 94 | self.is_ai = False 95 | -------------------------------------------------------------------------------- /genetic_algo/gomoku_web_genetic.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/env python 2 | import gomoku 3 | 4 | class Gomoku_Web(gomoku.Gomoku): 5 | """ Gomoku Game Rules: 6 | Two players alternatively put their stone on the board. First one got five in a row wins. 7 | """ 8 | def __init__(self, board_size=15, players=None, silent_mode=False, winning_num=5, score=[]): 9 | self.reset() 10 | self.board_size = board_size 11 | self.fastmode = None 12 | self.playing = None 13 | self.silent_mode = silent_mode 14 | if players: 15 | self.players = [Player(player_name) for player_name in players] 16 | else: 17 | self.players = [] 18 | self.last_move = None 19 | self.first_center = None 20 | self.winning_num = winning_num 21 | self.score = score 22 | 23 | def reset(self): 24 | self.board = (set(),set()) 25 | self.i_turn = 0 26 | self.hist_moves = [] 27 | self.winning_stones = set() 28 | 29 | def web_play(self, action): 30 | """ Controling the game play by the web function call""" 31 | self.playing = self.i_turn % 2 32 | current_player = self.players[self.playing] 33 | # print current_player 34 | if action == (0, 0): 35 | print("Player %s admit defeat!" % current_player.name) 36 | self.last_move = action 37 | # print self.last_move 38 | if self.place_stone() is False: 39 | return 40 | self.hist_moves.append(self.last_move) # for undo 41 | winner = self.check_winner() 42 | if not self.silent_mode: 43 | self.print_board() 44 | if winner is not None: 45 | print("########## %s is the WINNER! #########" % current_player.name) 46 | return None, winner 47 | self.i_turn += 1 48 | if self.i_turn == self.board_size ** 2: 49 | print("This game is a Draw!") 50 | return None, "Draw" 51 | next_action = self.check_next_ai() 52 | 53 | return next_action, winner 54 | 55 | def check_next_ai(self): 56 | """ check if the next move is AI """ 57 | self.playing = self.i_turn % 2 58 | current_player = self.players[self.playing] 59 | if current_player.name == 'parent': 60 | action = current_player.strategy(self.state, self.score[0]) 61 | return action 62 | elif current_player.name == 'child': 63 | action = current_player.strategy(self.state, self.score[1]) 64 | return action 65 | else: 66 | return None 67 | 68 | def undo(self): 69 | """ Undo opponent's last move and my last move """ 70 | if len(self.hist_moves) == 0: return 71 | opponent = int(not self.playing) 72 | opponent_last_move = self.hist_moves.pop() 73 | self.board[opponent].remove(opponent_last_move) 74 | if len(self.hist_moves) == 0: return 75 | my_last_move = self.hist_moves.pop() 76 | self.board[self.playing].remove(my_last_move) 77 | print("Undo!") 78 | if not self.silent_mode: 79 | self.print_board() 80 | 81 | 82 | class Player(gomoku.Player): 83 | def __init__(self, name, ai_script=None, level=None, score=[]): 84 | self.name = name 85 | # search for the strategy file 86 | self.score = score 87 | if ai_script is not None: 88 | print('Loading AI script %s'%ai_script) 89 | p = __import__(ai_script) 90 | try: 91 | self.strategy = p.strategy 92 | except: 93 | raise RuntimeError("Function strategy(state) is not found in %s"%ai_script) 94 | try: 95 | self.finish = p.finish 96 | except: 97 | pass 98 | self.is_ai = True 99 | if level: 100 | p.estimate_level = level 101 | else: 102 | self.is_ai = False 103 | -------------------------------------------------------------------------------- /genetic_algo/train_genetic.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": { 7 | "collapsed": true 8 | }, 9 | "outputs": [], 10 | "source": [ 11 | "import copy\n", 12 | "import numpy as np\n", 13 | "from random import choice\n", 14 | "from genetic_AI import strategy\n", 15 | "from gomoku_web_genetic import *" 16 | ] 17 | }, 18 | { 19 | "cell_type": "code", 20 | "execution_count": null, 21 | "metadata": { 22 | "collapsed": true 23 | }, 24 | "outputs": [], 25 | "source": [ 26 | "board_size = 15\n", 27 | "train_times = 500" 28 | ] 29 | }, 30 | { 31 | "cell_type": "code", 32 | "execution_count": null, 33 | "metadata": { 34 | "collapsed": true 35 | }, 36 | "outputs": [], 37 | "source": [ 38 | "def play_game(AI1_score, AI2_score):\n", 39 | " players = ['parent', 'child']\n", 40 | " game = Gomoku_Web(board_size=15, players=players, score=[AI1_score, AI2_score], silent_mode=True)\n", 41 | " \n", 42 | " p = __import__(\"genetic_AI\")\n", 43 | " game.players[0].strategy = p.strategy\n", 44 | " game.players[0].strategy.is_ai=True\n", 45 | " game.players[0].score=score[0]\n", 46 | " game.players[1].strategy = p.strategy\n", 47 | " game.players[1].strategy.is_ai=True\n", 48 | " game.players[1].score=score[1]\n", 49 | "\n", 50 | " center = ((board_size+1) / 2, (board_size+1) / 2)\n", 51 | " next_action, winner = game.web_play(center)\n", 52 | " while not winner:\n", 53 | " next_action, winner = game.web_play(next_action)\n", 54 | " game.print_board()\n", 55 | "\n", 56 | " print winner\n", 57 | " return winner" 58 | ] 59 | }, 60 | { 61 | "cell_type": "code", 62 | "execution_count": null, 63 | "metadata": { 64 | "collapsed": true 65 | }, 66 | "outputs": [], 67 | "source": [ 68 | "score_original = [1000000, 20000, 6100, 6000, 1100, 1000, 300, 290, 290, 290, 100, 10, 3, 1, \n", 69 | " 1000000, 100000, 65000, 65000, 5500, 5000, 200, 200, 200, 200, 90, 9, 4, 1]" 70 | ] 71 | }, 72 | { 73 | "cell_type": "code", 74 | "execution_count": null, 75 | "metadata": { 76 | "collapsed": true 77 | }, 78 | "outputs": [], 79 | "source": [ 80 | "def get_new_score(score):\n", 81 | " changed = choice(np.arange(len(score)))\n", 82 | " new_score = score_original[:changed]\n", 83 | " new_score.append(score[changed] * 1.03)\n", 84 | " new_score.extend(score[changed+1: ])\n", 85 | " return new_score" 86 | ] 87 | }, 88 | { 89 | "cell_type": "code", 90 | "execution_count": null, 91 | "metadata": { 92 | "collapsed": true 93 | }, 94 | "outputs": [], 95 | "source": [ 96 | "score_child = get_new_score(score_original)" 97 | ] 98 | }, 99 | { 100 | "cell_type": "code", 101 | "execution_count": null, 102 | "metadata": { 103 | "collapsed": true 104 | }, 105 | "outputs": [], 106 | "source": [ 107 | "score = [score_original, score_child]" 108 | ] 109 | }, 110 | { 111 | "cell_type": "code", 112 | "execution_count": null, 113 | "metadata": { 114 | "collapsed": true 115 | }, 116 | "outputs": [], 117 | "source": [ 118 | "latest_score = score_original" 119 | ] 120 | }, 121 | { 122 | "cell_type": "code", 123 | "execution_count": null, 124 | "metadata": {}, 125 | "outputs": [], 126 | "source": [ 127 | "for i in range(train_times):\n", 128 | " print 'Round', i+1\n", 129 | " if play_game(score_original, score_child) == 'child':\n", 130 | " print 'Update!'\n", 131 | " score_original = copy.deepcopy(score_child)\n", 132 | " score_child = get_new_score(score_original)\n", 133 | " latest_score = score_original\n", 134 | " else:\n", 135 | " score_child = get_new_score(score_original)\n", 136 | " print 'Find a new child!'" 137 | ] 138 | }, 139 | { 140 | "cell_type": "code", 141 | "execution_count": null, 142 | "metadata": { 143 | "collapsed": true 144 | }, 145 | "outputs": [], 146 | "source": [] 147 | } 148 | ], 149 | "metadata": { 150 | "kernelspec": { 151 | "display_name": "Python [conda root]", 152 | "language": "python", 153 | "name": "conda-root-py" 154 | }, 155 | "language_info": { 156 | "codemirror_mode": { 157 | "name": "ipython", 158 | "version": 2 159 | }, 160 | "file_extension": ".py", 161 | "mimetype": "text/x-python", 162 | "name": "python", 163 | "nbconvert_exporter": "python", 164 | "pygments_lexer": "ipython2", 165 | "version": "2.7.13" 166 | } 167 | }, 168 | "nbformat": 4, 169 | "nbformat_minor": 2 170 | } 171 | -------------------------------------------------------------------------------- /AI1.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | 4 | # ---point table--- 5 | # // tuple is empty 6 | # Blank, tupleScoreTable[0] = 7; 7 | # // tuple contains a black chess 8 | # B, tupleScoreTable[1] = 35; 9 | # // tuple contains two black chesses 10 | # BB, tupleScoreTable[2] = 800; 11 | # // tuple contains three black chesses 12 | # BBB, tupleScoreTable[3] = 15000; 13 | # // tuple contains four black chesses 14 | # BBBB, tupleScoreTable[4] = 800000; 15 | # // tuple contains a white chess 16 | # W, tupleScoreTable[5] = 15; 17 | # // tuple contains two white chesses 18 | # WW, tupleScoreTable[6] = 400; 19 | # // tuple contains three white chesses 20 | # WWW, tupleScoreTable[7] = 1800; 21 | # // tuple contains four white chesses 22 | # WWWW, tupleScoreTable[8] = 100000; 23 | # // tuple does not exist 24 | # Virtual, tupleScoreTable[9] = 0; 25 | # // tuple contains at least one black and at least one white 26 | # Polluted, tupleScoreTable[10] = 0 27 | import numpy as np 28 | from random import choice 29 | 30 | def strategy(state): 31 | """ Information provided to you: 32 | state = (board, last_move, playing, board_size) 33 | board = (x_stones, o_stones) 34 | stones is a set contains positions of one player's stones. e.g. 35 | x_stones = {(8,8), (8,9), (8,10), (8,11)} 36 | playing = 0|1, the current player's index 37 | 38 | Your strategy will return a position code for the next stone, e.g. (8,7) 39 | """ 40 | board, last_move, playing, board_size = state 41 | row = board_size 42 | col = board_size 43 | # create a table to record the board state 44 | # 1: occupied by self 45 | # -1: occupied by opponent 46 | # 0: available 47 | table = np.zeros([row, col]) 48 | for i in range(row): 49 | for j in range(col): 50 | if playing == 1: 51 | if (i+1, j+1) in board[0]: 52 | table[i, j] = -1 53 | elif (i+1, j+1) in board[1]: 54 | table[i, j] = 1 55 | else: 56 | if (i+1, j+1) in board[0]: 57 | table[i, j] = 1 58 | elif (i+1, j+1) in board[1]: 59 | table[i, j] = -1 60 | 61 | def score(fiveTuple): 62 | if len(fiveTuple) != 5: 63 | print "ERROR" 64 | return None 65 | if 1 in fiveTuple and -1 in fiveTuple: 66 | return 0 67 | elif sum(fiveTuple) == 0: 68 | return 7 69 | elif sum(fiveTuple) == -1: 70 | return -35 71 | elif sum(fiveTuple) == -2: 72 | return -800 73 | elif sum(fiveTuple) == -3: 74 | return -15000 75 | elif sum(fiveTuple) == -4: 76 | return -800000 77 | elif sum(fiveTuple) == -5: 78 | return -10000000 79 | elif sum(fiveTuple) == 5: 80 | return 10000000 81 | elif sum(fiveTuple) == 1: 82 | return 15 83 | elif sum(fiveTuple) == 2: 84 | return 400 85 | elif sum(fiveTuple) == 3: 86 | return 1800 87 | elif sum(fiveTuple) == 4: 88 | return 100000 89 | 90 | 91 | def heuristic(table): 92 | sumScore = 0 93 | for i in range(row): 94 | for j in range(col): 95 | if j+4 < col: 96 | sumScore += score(tuple(table[i, j:j+5])) 97 | if i+4 < row: 98 | sumScore += score(tuple(table[i:i+5, j])) 99 | if i+4 < row and j+4 < col: 100 | fivetuple = [] 101 | for k in range(5): 102 | fivetuple.append(table[i+k, j+k]) 103 | sumScore += score(tuple(fivetuple)) 104 | if i+4 < row and j-4 >= 0: 105 | fivetuple = [] 106 | for k in range(5): 107 | fivetuple.append(table[i+k, j-k]) 108 | sumScore += score(tuple(fivetuple)) 109 | return sumScore 110 | 111 | def randomChoose(scoretable): 112 | maxValue = max(scoretable.items(), key=lambda x: x[1])[1] 113 | positions=[] 114 | for item in scoretable.items(): 115 | if item[1]==maxValue: 116 | positions.append(item[0]) 117 | return choice(positions) 118 | 119 | if len(board[0]) == 0 and len(board[1]) == 0: 120 | return (board_size/2 + 1, board_size/2 + 1) 121 | else: 122 | scoreTable = {} 123 | for i in range(row): 124 | for j in range(col): 125 | if table[i, j] == 0: 126 | table[i, j] = 1 127 | scoreTable[(i, j)] = heuristic(table) 128 | table[i, j] = 0 129 | self_position = randomChoose(scoreTable) 130 | return (self_position[0]+1, self_position[1]+1) 131 | 132 | def finish(): 133 | pass 134 | -------------------------------------------------------------------------------- /app/static/js/main.js: -------------------------------------------------------------------------------- 1 | /* Copied and Modified from opensource project https://github.com/lihongxun945/gobang */ 2 | var Board = function(container, status) { 3 | this.container = container; 4 | this.status = status; 5 | this.step = this.container.width() * 0.065; 6 | this.offset = this.container.width() * 0.044; 7 | this.steps = []; 8 | 9 | this.started = false; 10 | this.container_x = container.offset().left; 11 | this.container_y = container.offset().top; 12 | 13 | var self = this; 14 | this.container.on("click", function(e) { 15 | if(self.lock || !self.started) return; 16 | var y = e.pageX - container.offset().left, x = e.pageY - container.offset().top; 17 | 18 | x = Math.floor((x+self.offset)/self.step) - 1; 19 | y = Math.floor((y+self.offset)/self.step) - 1; 20 | 21 | self.set(x, y, self.playing); 22 | }); 23 | 24 | 25 | this.setStatus("Welcome to Gomoku"); 26 | 27 | } 28 | 29 | 30 | 31 | Board.prototype.start = function() { 32 | if(this.started) return; 33 | this.p1 = $('select[name="BlackPlayer"]').val() 34 | this.p2 = $('select[name="WhitePlayer"]').val() 35 | this.level = $('input[name="AI_Level"]').val() 36 | this.initBoard(); 37 | this.init_server(); 38 | this.draw(); 39 | this.setStatus("Game Started!"); 40 | this.started = true; 41 | this.playing = 2; 42 | if(this.p1.startsWith('AI')) { 43 | this.set(7, 7, 2) 44 | } 45 | } 46 | 47 | Board.prototype.stop = function() { 48 | if(!this.started) return; 49 | this.setStatus("Please Click Start"); 50 | this.started = false; 51 | this.reset_server(); 52 | } 53 | 54 | Board.prototype.initBoard = function() { 55 | this.board = []; 56 | for(var i=0;i<15;i++) { 57 | var row = []; 58 | for(var j=0;j<15;j++) { 59 | row.push(0); 60 | } 61 | this.board.push(row); 62 | } 63 | this.steps = []; 64 | } 65 | 66 | Board.prototype.draw = function() { 67 | var container = this.container; 68 | var board = this.board; 69 | 70 | container.find(".chessman, .indicator").remove(); 71 | 72 | for(var i=0;i").appendTo(container); 76 | if(board[i][j] == 2) chessman.addClass("black"); 77 | chessman.css("top", this.offset + i*this.step); 78 | chessman.css("left", this.offset + j*this.step); 79 | } 80 | } 81 | } 82 | 83 | if(this.steps.length > 0) { 84 | var lastStep = this.steps[this.steps.length-1]; 85 | $("
") 86 | .appendTo(container) 87 | .css("top", this.offset + this.step * lastStep[0]) 88 | .css("left", this.offset + this.step * lastStep[1]) 89 | } 90 | 91 | } 92 | 93 | Board.prototype.set = function(x, y, role) { 94 | if(this.board[x][y] !== 0) { 95 | return; 96 | } 97 | this.set_server(x,y); 98 | this.board[x][y] = role; 99 | this.steps.push([x,y]); 100 | this.draw(); 101 | if(this.playing == 2) {this.playing = 1;} 102 | else if(this.playing == 1) {this.playing = 2;} 103 | } 104 | 105 | Board.prototype.setStatus = function(s) { 106 | this.status.text(s); 107 | } 108 | 109 | Board.prototype.undo = function(step) { 110 | if(this.lock) { 111 | this.setStatus("Please wait while AI running."); 112 | return; 113 | } 114 | this.undo_server(); 115 | step = step || 1; 116 | while(step && this.steps.length >= 2) { 117 | var s = this.steps.pop(); 118 | this.board[s[0]][s[1]] = 0; 119 | s = this.steps.pop(); 120 | this.board[s[0]][s[1]] = 0; 121 | step --; 122 | } 123 | this.draw(); 124 | } 125 | 126 | // Server functions passed to Flask 127 | Board.prototype.set_server = function(x,y) { 128 | if(this.board[x][y] !== 0) { 129 | return; 130 | } 131 | this.lock = true; 132 | var self = this; 133 | $.getJSON($SCRIPT_ROOT + '/_player_set', {position: x.toString() + ',' + y.toString()}, function(data) { 134 | //console.log(data); 135 | var ai_move = data.next_move; 136 | var winner = data.winner; 137 | if (ai_move !== null) { 138 | self.set(ai_move[0], ai_move[1], self.playing); 139 | } 140 | if (winner !== null) { 141 | $.alert(winner+" Win!", function() {self.stop();}); 142 | } 143 | self.lock = false; 144 | }); 145 | 146 | }; 147 | 148 | Board.prototype.init_server = function() { 149 | $.getJSON($SCRIPT_ROOT + '/_start', { 150 | p1: this.p1, 151 | p2: this.p2, 152 | lv: this.level, 153 | }, function(data){}); 154 | }; 155 | 156 | 157 | Board.prototype.reset_server = function() { 158 | $.getJSON($SCRIPT_ROOT + '/_reset', {}, function(data){}); 159 | }; 160 | 161 | Board.prototype.undo_server = function() { 162 | $.getJSON($SCRIPT_ROOT + '/_undo', {}, function(data){}); 163 | }; 164 | 165 | 166 | var b = new Board($("#board"), $(".status")); 167 | b.reset_server() 168 | $("#start").click(function() { 169 | b.start(); 170 | }); 171 | 172 | $("#fail").click(function() { 173 | $.confirm("Sure you want to resign?", function() { 174 | b.stop(); 175 | }); 176 | }); 177 | 178 | $("#undo").click(function() { 179 | b.undo(); 180 | }); 181 | -------------------------------------------------------------------------------- /AI6.py: -------------------------------------------------------------------------------- 1 | # -- coding: utf-8 -- 2 | import numpy as np 3 | import random 4 | from random import choice 5 | import os 6 | 7 | basedir = os.path.abspath(os.path.dirname(__file__)) 8 | record = np.load(os.path.join(basedir, 'game_record', 'records.npy')) 9 | record = map(lambda x: x.strip(), record) 10 | 11 | def change_format(game_record): 12 | return game_record.split(';') 13 | 14 | record = map(change_format, record) 15 | record = np.array(record) 16 | 17 | alphabet = 'abcdefghijklmno' 18 | alpha_dic = dict(zip(alphabet, np.arange(1, 15))) 19 | 20 | def match_record(record, stone_num, board): 21 | try: 22 | stones = record[:stone_num] 23 | white = [stones[i] for i in range(stone_num) if i % 2 == 1] 24 | black = [stones[i] for i in range(stone_num) if i % 2 == 0] 25 | white = map(lambda x: x[2:-1], white) 26 | black = map(lambda x: x[2:-1], black) 27 | white = set([(alpha_dic[i[0]], alpha_dic[i[1]]) for i in white]) 28 | black = set([(alpha_dic[i[0]], alpha_dic[i[1]]) for i in black]) 29 | record_board = (black, white) 30 | if board == record_board: 31 | return True 32 | else: 33 | return False 34 | except: 35 | return False 36 | 37 | def find_best_place(state): 38 | """ Information provided to you: 39 | state = (board, last_move, playing, board_size) 40 | board = (x_stones, o_stones) 41 | stones is a set contains positions of one player's stones. e.g. 42 | x_stones = {(8,8), (8,9), (8,10), (8,11)} 43 | playing = 0|1, the current player's index 44 | 45 | Your strategy will return a position code for the next stone, e.g. (8,7) 46 | """ 47 | board, last_move, playing, board_size = state 48 | row = board_size 49 | col = board_size 50 | # create a table to record the board state 51 | # 1: occupied by self 52 | # -1: occupied by opponent 53 | # 0: available 54 | table = np.zeros([row, col]) 55 | for i in range(row): 56 | for j in range(col): 57 | if playing == 1: 58 | if (i+1, j+1) in board[0]: 59 | table[i, j] = -1 60 | elif (i+1, j+1) in board[1]: 61 | table[i, j] = 1 62 | else: 63 | if (i+1, j+1) in board[0]: 64 | table[i, j] = 1 65 | elif (i+1, j+1) in board[1]: 66 | table[i, j] = -1 67 | 68 | def score(fiveTuple): 69 | if len(fiveTuple) != 5: 70 | print "ERROR" 71 | return None 72 | if 1 in fiveTuple and -1 in fiveTuple: 73 | return 0 74 | elif sum(fiveTuple) == 0: 75 | return 7 76 | elif sum(fiveTuple) == -1: 77 | return -35 78 | elif sum(fiveTuple) == -2: 79 | return -800 80 | elif sum(fiveTuple) == -3: 81 | return -15000 82 | elif sum(fiveTuple) == -4: 83 | return -800000 84 | elif sum(fiveTuple) == -5: 85 | return -10000000 86 | elif sum(fiveTuple) == 5: 87 | return 10000000 88 | elif sum(fiveTuple) == 1: 89 | return 15 90 | elif sum(fiveTuple) == 2: 91 | return 400 92 | elif sum(fiveTuple) == 3: 93 | return 1800 94 | elif sum(fiveTuple) == 4: 95 | return 100000 96 | 97 | 98 | def heuristic(table): 99 | sumScore = 0 100 | for i in range(row): 101 | for j in range(col): 102 | if j+4 < col: 103 | sumScore += score(tuple(table[i, j:j+5])) 104 | if i+4 < row: 105 | sumScore += score(tuple(table[i:i+5, j])) 106 | if i+4 < row and j+4 < col: 107 | fivetuple = [] 108 | for k in range(5): 109 | fivetuple.append(table[i+k, j+k]) 110 | sumScore += score(tuple(fivetuple)) 111 | if i+4 < row and j-4 >= 0: 112 | fivetuple = [] 113 | for k in range(5): 114 | fivetuple.append(table[i+k, j-k]) 115 | sumScore += score(tuple(fivetuple)) 116 | return sumScore 117 | 118 | def randomChoose(scoretable): 119 | maxValue = max(scoretable.items(), key=lambda x: x[1])[1] 120 | positions=[] 121 | for item in scoretable.items(): 122 | if item[1]==maxValue: 123 | positions.append(item[0]) 124 | return choice(positions) 125 | 126 | if len(board[0]) == 0 and len(board[1]) == 0: 127 | return (board_size/2 + 1, board_size/2 + 1) 128 | else: 129 | scoreTable = {} 130 | for i in range(row): 131 | for j in range(col): 132 | if table[i, j] == 0: 133 | table[i, j] = 1 134 | scoreTable[(i, j)] = heuristic(table) 135 | table[i, j] = 0 136 | self_position = randomChoose(scoreTable) 137 | return (self_position[0]+1, self_position[1]+1) 138 | 139 | def strategy(state): 140 | """ Information provided to you: 141 | state = (board, last_move, playing, board_size) 142 | board = (x_stones, o_stones) 143 | stones is a set contains positions of one player's stones. e.g. 144 | x_stones = {(8,8), (8,9), (8,10), (8,11)} 145 | playing = 0|1, the current player's index 146 | This strategy will search 5570 games and return a matched next step, e.g. (8,7) 147 | """ 148 | board, last_move, playing, board_size = state 149 | x, y = last_move 150 | turns = len(board[0]) + len(board[1]) 151 | match_game = record[map(lambda x: match_record(x, turns, board), record)] 152 | if len(match_game): 153 | print len(match_game), 'game matched!' 154 | select_game = random.choice(match_game) 155 | next_place = select_game[turns] 156 | x, y = alpha_dic[next_place[2]], alpha_dic[next_place[3]] 157 | return (x, y) 158 | else: 159 | return find_best_place(state) 160 | -------------------------------------------------------------------------------- /AI2.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import numpy as np 3 | import random 4 | 5 | # test_board = ({(8, 8), (9, 9)}, {(8, 9)}) 6 | # test_playing = 1 7 | # test_last_move = (8, 8) 8 | # test_board_size = 15 9 | # test_state = (test_board, test_last_move, test_playing, test_board_size) 10 | 11 | def strategy(state): 12 | """ Information provided to you: 13 | state = (board, last_move, playing, board_size) 14 | board = (x_stones, o_stones) 15 | stones is a set contains positions of one player's stones. e.g. 16 | x_stones = {(8,8), (8,9), (8,10), (8,11)} 17 | playing = 0|1, the current player's index 18 | 19 | Your strategy will return a position code for the next stone, e.g. (8,7) 20 | """ 21 | board, last_move, playing, board_size = state 22 | if last_move == None: 23 | return (board_size/2 + 1, board_size/2 + 1) 24 | 25 | root = construct_tree(state, 0, 2) 26 | max_value = float("-inf") 27 | best_move = [] 28 | for successor in root.successor: 29 | successor_value = maxValue(successor, float("-inf"), float("inf")) 30 | if successor_value > max_value: 31 | max_value = successor_value 32 | best_move = list(successor.state[0][playing] - board[playing]) 33 | elif successor_value == max_value: 34 | best_move.extend(list(successor.state[0][playing] - board[playing])) 35 | if best_move == []: 36 | best_move = random.choice(find_avai_position(state)) 37 | return random.choice(best_move) 38 | 39 | 40 | def maxValue(node, alpha, beta): 41 | if node.isLeaf: 42 | return node.value 43 | v = float("-inf") 44 | for successor in node.successor: 45 | v = max(v, minValue(successor, alpha, beta)) 46 | if v >= beta: 47 | return v 48 | alpha = max(alpha, v) 49 | return v 50 | 51 | 52 | def minValue(node, alpha, beta): 53 | if node.isLeaf: 54 | return node.value 55 | v = float("inf") 56 | for successor in node.successor: 57 | v = min(v, maxValue(successor, alpha, beta)) 58 | if v <= alpha: 59 | return v 60 | beta = min(beta, v) 61 | return v 62 | 63 | 64 | def find_avai_position(state): 65 | board, last_move, playing, board_size = state 66 | my_stones = board[playing] 67 | oppo_stones = board[not playing] 68 | 69 | avai_position = [] 70 | for stone in my_stones: 71 | avai_position.extend(find_around_position(stone)) 72 | for stone in oppo_stones: 73 | avai_position.extend(find_around_position(stone)) 74 | 75 | avai_position = set(avai_position) 76 | for stone in my_stones: 77 | avai_position.remove(stone) 78 | for stone in oppo_stones: 79 | avai_position.remove(stone) 80 | avai_position = {pos for pos in avai_position 81 | if pos[0] in range(1, board_size+1) 82 | and pos[1] in range(1, board_size+1)} 83 | return avai_position 84 | 85 | 86 | def find_around_position(position): 87 | x, y = position 88 | return [(x_, y_) for x_ in [x-1, x, x+1] for y_ in [y-1, y, y+1]] 89 | 90 | 91 | def score(fiveTuple): 92 | if len(fiveTuple) != 5: 93 | print "ERROR" 94 | return None 95 | if 1 in fiveTuple and -1 in fiveTuple: 96 | return 0 97 | elif sum(fiveTuple) == 0: 98 | return 7 99 | elif sum(fiveTuple) == -1: 100 | return -35 101 | elif sum(fiveTuple) == -2: 102 | return -800 103 | elif sum(fiveTuple) == -3: 104 | return -15000 105 | elif sum(fiveTuple) == -4: 106 | return -800000 107 | elif sum(fiveTuple) == -5: 108 | return -10000000 109 | elif sum(fiveTuple) == 1: 110 | return 15 111 | elif sum(fiveTuple) == 2: 112 | return 400 113 | elif sum(fiveTuple) == 3: 114 | return 1800 115 | elif sum(fiveTuple) == 4: 116 | return 100000 117 | elif sum(fiveTuple) == 5: 118 | return 10000000 119 | 120 | 121 | def get_value(state): 122 | board, last_move, playing, board_size = state 123 | row = board_size 124 | col = board_size 125 | # create a table to record the board state 126 | # 1: occupied by self 127 | # -1: occupied by opponent 128 | # 0: available 129 | table = np.zeros([row, col]) 130 | for i in range(row): 131 | for j in range(col): 132 | if playing == 1: 133 | if (i+1, j+1) in board[0]: 134 | table[i, j] = -1 135 | elif (i+1, j+1) in board[1]: 136 | table[i, j] = 1 137 | else: 138 | if (i+1, j+1) in board[0]: 139 | table[i, j] = 1 140 | elif (i+1, j+1) in board[1]: 141 | table[i, j] = -1 142 | sumScore = 0 143 | for i in range(row): 144 | for j in range(col): 145 | if j+4 < col: 146 | sumScore += score(tuple(table[i, j:j+5])) 147 | if i+4 < row: 148 | sumScore += score(tuple(table[i:i+5, j])) 149 | if i+4 < row and j+4 < col: 150 | fivetuple = [] 151 | for k in range(5): 152 | fivetuple.append(table[i+k, j+k]) 153 | sumScore += score(tuple(fivetuple)) 154 | if i+4 < row and j-4 >= 0: 155 | fivetuple = [] 156 | for k in range(5): 157 | fivetuple.append(table[i+k, j-k]) 158 | sumScore += score(tuple(fivetuple)) 159 | return sumScore 160 | 161 | 162 | class Node: 163 | def __init__(self, state, depth, limitedDepth, successor = [],isLeaf = False, value = None): 164 | self.state = state 165 | self.depth = depth 166 | self.limitedDepth = limitedDepth 167 | self.successor = successor 168 | self.isLeaf = isLeaf 169 | self.value = value 170 | 171 | def __repr__(self): 172 | info = '========= Node Info ========' 173 | depth = 'depth:' + str(self.depth) 174 | limitedDepth = 'limitedDepth:' + str(self.limitedDepth) 175 | isLeaf = 'isLeaf:' + str(self.isLeaf) 176 | value = 'value:' + str(self.value) 177 | state = 'state:' + str(self.state) 178 | successor = 'successors:' + str(len(self.successor)) 179 | return '\n'.join([info, depth, limitedDepth, isLeaf, value, state, successor, '\n']) 180 | 181 | 182 | def construct_tree(state, depth, limitedDepth): 183 | board, last_move, playing, board_size = state 184 | oppo_stones = board[not playing].copy() 185 | 186 | tree_root = Node(state, depth, limitedDepth, successor=[]) 187 | 188 | if depth==limitedDepth: 189 | tree_root.isLeaf = True 190 | tree_root.value = get_value(state) 191 | return tree_root 192 | 193 | avai_position = find_avai_position(state) 194 | for position in avai_position: 195 | my_stones = board[playing].copy() 196 | my_stones.add(position) 197 | if playing == 0: 198 | new_board = (my_stones, oppo_stones) 199 | next_playing = 1 200 | else: 201 | new_board = (oppo_stones, my_stones) 202 | next_playing = 0 203 | new_state = (new_board, position, next_playing, board_size) 204 | tree_root.successor.append(construct_tree(new_state, depth+1, limitedDepth)) 205 | return tree_root 206 | 207 | 208 | def finish(): 209 | pass 210 | 211 | # import time 212 | # start = time.clock() 213 | # print strategy(test_state) 214 | # end = time.clock() 215 | # print start 216 | # print end 217 | # print end-start 218 | -------------------------------------------------------------------------------- /clean_game_records.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "collapsed": true 8 | }, 9 | "outputs": [], 10 | "source": [ 11 | "import os" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": 2, 17 | "metadata": {}, 18 | "outputs": [ 19 | { 20 | "name": "stdout", 21 | "output_type": "stream", 22 | "text": [ 23 | "/Users/tianxiaohu/Programming/GomokuAgent/game_record\n" 24 | ] 25 | } 26 | ], 27 | "source": [ 28 | "cd game_record/" 29 | ] 30 | }, 31 | { 32 | "cell_type": "code", 33 | "execution_count": 3, 34 | "metadata": { 35 | "collapsed": true 36 | }, 37 | "outputs": [], 38 | "source": [ 39 | "a = os.listdir('../game_record/')" 40 | ] 41 | }, 42 | { 43 | "cell_type": "code", 44 | "execution_count": 4, 45 | "metadata": { 46 | "collapsed": true 47 | }, 48 | "outputs": [], 49 | "source": [ 50 | "records = []" 51 | ] 52 | }, 53 | { 54 | "cell_type": "code", 55 | "execution_count": 5, 56 | "metadata": {}, 57 | "outputs": [], 58 | "source": [ 59 | "for record in a:\n", 60 | " f = open(record)\n", 61 | " line = f.readline()\n", 62 | " records.append(line[line.find('B[hh]'):-4])" 63 | ] 64 | }, 65 | { 66 | "cell_type": "code", 67 | "execution_count": 6, 68 | "metadata": { 69 | "collapsed": true 70 | }, 71 | "outputs": [], 72 | "source": [ 73 | "records = map(lambda x: x.strip(), records)" 74 | ] 75 | }, 76 | { 77 | "cell_type": "code", 78 | "execution_count": 7, 79 | "metadata": { 80 | "collapsed": true 81 | }, 82 | "outputs": [], 83 | "source": [ 84 | "import numpy as np" 85 | ] 86 | }, 87 | { 88 | "cell_type": "code", 89 | "execution_count": 8, 90 | "metadata": {}, 91 | "outputs": [], 92 | "source": [ 93 | "while '' in records:\n", 94 | " records.remove('')" 95 | ] 96 | }, 97 | { 98 | "cell_type": "code", 99 | "execution_count": 9, 100 | "metadata": {}, 101 | "outputs": [ 102 | { 103 | "data": { 104 | "text/plain": [ 105 | "5570" 106 | ] 107 | }, 108 | "execution_count": 9, 109 | "metadata": {}, 110 | "output_type": "execute_result" 111 | } 112 | ], 113 | "source": [ 114 | "len(records)" 115 | ] 116 | }, 117 | { 118 | "cell_type": "code", 119 | "execution_count": 10, 120 | "metadata": {}, 121 | "outputs": [], 122 | "source": [ 123 | "def get_win(x):\n", 124 | " try:\n", 125 | " return x[-5]\n", 126 | " except:\n", 127 | " print x" 128 | ] 129 | }, 130 | { 131 | "cell_type": "code", 132 | "execution_count": 11, 133 | "metadata": {}, 134 | "outputs": [ 135 | { 136 | "data": { 137 | "text/plain": [ 138 | "array([';', 'B', 'W'], \n", 139 | " dtype='|S1')" 140 | ] 141 | }, 142 | "execution_count": 11, 143 | "metadata": {}, 144 | "output_type": "execute_result" 145 | } 146 | ], 147 | "source": [ 148 | "np.unique(map(get_win, records))" 149 | ] 150 | }, 151 | { 152 | "cell_type": "code", 153 | "execution_count": 12, 154 | "metadata": { 155 | "collapsed": true 156 | }, 157 | "outputs": [], 158 | "source": [ 159 | "winner = map(get_win, records)" 160 | ] 161 | }, 162 | { 163 | "cell_type": "code", 164 | "execution_count": 13, 165 | "metadata": {}, 166 | "outputs": [ 167 | { 168 | "data": { 169 | "text/plain": [ 170 | "list" 171 | ] 172 | }, 173 | "execution_count": 13, 174 | "metadata": {}, 175 | "output_type": "execute_result" 176 | } 177 | ], 178 | "source": [ 179 | "type(winner)" 180 | ] 181 | }, 182 | { 183 | "cell_type": "code", 184 | "execution_count": 14, 185 | "metadata": { 186 | "collapsed": true 187 | }, 188 | "outputs": [], 189 | "source": [ 190 | "winner = np.array(winner)" 191 | ] 192 | }, 193 | { 194 | "cell_type": "code", 195 | "execution_count": 15, 196 | "metadata": {}, 197 | "outputs": [ 198 | { 199 | "data": { 200 | "text/plain": [ 201 | "2821" 202 | ] 203 | }, 204 | "execution_count": 15, 205 | "metadata": {}, 206 | "output_type": "execute_result" 207 | } 208 | ], 209 | "source": [ 210 | "len(winner[winner=='B'])" 211 | ] 212 | }, 213 | { 214 | "cell_type": "code", 215 | "execution_count": 16, 216 | "metadata": {}, 217 | "outputs": [ 218 | { 219 | "data": { 220 | "text/plain": [ 221 | "2747" 222 | ] 223 | }, 224 | "execution_count": 16, 225 | "metadata": {}, 226 | "output_type": "execute_result" 227 | } 228 | ], 229 | "source": [ 230 | "len(winner[winner=='W'])" 231 | ] 232 | }, 233 | { 234 | "cell_type": "code", 235 | "execution_count": 17, 236 | "metadata": {}, 237 | "outputs": [ 238 | { 239 | "data": { 240 | "text/plain": [ 241 | "2" 242 | ] 243 | }, 244 | "execution_count": 17, 245 | "metadata": {}, 246 | "output_type": "execute_result" 247 | } 248 | ], 249 | "source": [ 250 | "len(winner[winner==';'])" 251 | ] 252 | }, 253 | { 254 | "cell_type": "code", 255 | "execution_count": 18, 256 | "metadata": {}, 257 | "outputs": [ 258 | { 259 | "data": { 260 | "text/plain": [ 261 | "array([ 'B[hh];W[ii];B[ff];W[gg];B[hi];W[ig];B[hg];W[hf];B[ih];W[jh];B[hj];W[hk];B[ge];W[fh];B[ie];W[he];B[hd];W[ic];B[gc];W[fb];B[gf];W[fe];B[gd];W[gb];B[fd];W[dd];B[je];W[kg];B[k]',\n", 262 | " 'B[hh];W[hi];B[jj];W[ii];B[jh];W[ih];B[ig];W[gi];B[ji];W[jg];B[gj];W[ik];B[ij];W[ei];B[fi];W[hj];B[jl];W[jk];B[kh];W[hk];B[gk];W[hf];B[eg];W[kj];B[il];W[kf];B[le];W[lg];B[je];W[mh];B[li];W[mg];B[mi];W[b0];B[k]'], \n", 263 | " dtype='|S971')" 264 | ] 265 | }, 266 | "execution_count": 18, 267 | "metadata": {}, 268 | "output_type": "execute_result" 269 | } 270 | ], 271 | "source": [ 272 | "np.array(records)[map(lambda x: x[-5]==';', records)]" 273 | ] 274 | }, 275 | { 276 | "cell_type": "code", 277 | "execution_count": 19, 278 | "metadata": { 279 | "collapsed": true 280 | }, 281 | "outputs": [], 282 | "source": [ 283 | "records = np.array(records)[map(lambda x: x[-5]!=';', records)]" 284 | ] 285 | }, 286 | { 287 | "cell_type": "code", 288 | "execution_count": 20, 289 | "metadata": {}, 290 | "outputs": [ 291 | { 292 | "data": { 293 | "text/plain": [ 294 | "(5568,)" 295 | ] 296 | }, 297 | "execution_count": 20, 298 | "metadata": {}, 299 | "output_type": "execute_result" 300 | } 301 | ], 302 | "source": [ 303 | "records.shape" 304 | ] 305 | }, 306 | { 307 | "cell_type": "code", 308 | "execution_count": 21, 309 | "metadata": { 310 | "collapsed": true 311 | }, 312 | "outputs": [], 313 | "source": [ 314 | "np.save('records.npy', records)" 315 | ] 316 | } 317 | ], 318 | "metadata": { 319 | "kernelspec": { 320 | "display_name": "Python [conda root]", 321 | "language": "python", 322 | "name": "conda-root-py" 323 | }, 324 | "language_info": { 325 | "codemirror_mode": { 326 | "name": "ipython", 327 | "version": 2 328 | }, 329 | "file_extension": ".py", 330 | "mimetype": "text/x-python", 331 | "name": "python", 332 | "nbconvert_exporter": "python", 333 | "pygments_lexer": "ipython2", 334 | "version": "2.7.13" 335 | } 336 | }, 337 | "nbformat": 4, 338 | "nbformat_minor": 2 339 | } 340 | -------------------------------------------------------------------------------- /AI5.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import numpy as np 3 | from random import choice 4 | 5 | def strategy(state): 6 | """ Information provided to you: 7 | state = (board, last_move, playing, board_size) 8 | board = (x_stones, o_stones) 9 | stones is a set contains positions of one player's stones. e.g. 10 | x_stones = {(8,8), (8,9), (8,10), (8,11)} 11 | playing = 0|1, the current player's index 12 | 13 | Your strategy will return a position code for the next stone, e.g. (8,7) 14 | """ 15 | board, last_move, playing, board_size = state 16 | row = board_size 17 | col = board_size 18 | # create a table to record the board state 19 | # 1: occupied by self 20 | # 2: occupied by opponent 21 | # 0: available 22 | table = np.zeros([row, col]) 23 | for i in range(row): 24 | for j in range(col): 25 | if playing == 1: 26 | if (i+1, j+1) in board[0]: 27 | table[i, j] = 2 28 | elif (i+1, j+1) in board[1]: 29 | table[i, j] = 1 30 | else: 31 | if (i+1, j+1) in board[0]: 32 | table[i, j] = 1 33 | elif (i+1, j+1) in board[1]: 34 | table[i, j] = 2 35 | # 获取该点4个方向的棋型 36 | def getstring(point): 37 | x = point[0] 38 | y = point[1] 39 | # vertical 40 | getLine1 = '' 41 | for k in range(max(x - 4, 0), min(x + 5, 15)): 42 | getLine1 += str(int(table[k, y])) 43 | if x-4<0: getLine1 = '*'+getLine1 44 | if x+4>14: getLine1 = getLine1+'*' 45 | # horizonal 46 | getLine2 = '' 47 | for k in range(max(y - 4, 0), min(y + 5, 15)): 48 | getLine2 += str(int(table[x, k])) 49 | if y-4<0: getLine2 = '*'+getLine2 50 | if y+4>14: getLine2 = getLine2+'*' 51 | # Oblique 45 52 | getLine3 = '' 53 | bx = max(0, x - 4) 54 | by = max(0, y - 4) 55 | ux = min(14, x + 4) 56 | uy = min(14, y + 4) 57 | for k in range(max(bx - x, by - y), min(ux - x, uy - y)+1): 58 | getLine3 += str(int(table[x + k, y + k])) 59 | if x-4<0 or y-4<0: getLine3 = '*'+getLine3 60 | if x+4>14 or y+4>14: getLine3 = getLine3+'*' 61 | # Oblique 135 62 | getLine4 = '' 63 | for k in range(max(bx - x, y - uy), min(ux - x, y - by)+1): 64 | getLine4 += str(int(table[x + k, y - k])) 65 | if x-4<0 or y+4>14: getLine4 = '*'+getLine4 66 | if x+4>14 or y-4<0: getLine4 = getLine4+'*' 67 | 68 | return [getLine1, getLine2, getLine3, getLine4] 69 | 70 | # 判断我方棋型 71 | def judgeType1(getline): 72 | if '11111' in getline: 73 | return 'win5' 74 | if '011110' in getline: 75 | return 'alive4' 76 | if '211110' in getline or '011112' in getline\ 77 | or '*11110' in getline or '01111*' in getline: 78 | return 'lian-rush4' 79 | if '11101' in getline or '10111' in getline\ 80 | or '11011' in getline: 81 | return 'tiao-rush4' 82 | if '001110' in getline or '011100' in getline: 83 | return 'lian-alive3' 84 | if '011010' in getline or '010110' in getline: 85 | return 'tiao-alive3' 86 | if '211100' in getline or '001112' in getline\ 87 | or '*11100' in getline or '00111*' in getline: 88 | return 'lian-sleep3' 89 | if '211010' in getline or '010112' in getline\ 90 | or '*11010' in getline or '01011*' in getline\ 91 | or '210110' in getline or '011012' in getline\ 92 | or '*10110' in getline or '01101*' in getline: 93 | return 'tiao-sleep3' 94 | if '11001' in getline or '10011' in getline\ 95 | or '10101' in getline: 96 | return 'te-sleep3' 97 | if '2011102' in getline or '*011102' in getline\ 98 | or '201110*' in getline or '*01110*' in getline: 99 | return 'jia-alive3' 100 | if '001100' in getline or '011000' in getline\ 101 | or '000110' in getline or '001010' in getline\ 102 | or '010100' in getline or '010010' in getline: 103 | return 'alive2' 104 | if '211000' in getline or '000112' in getline\ 105 | or '*11000' in getline or '00011*' in getline\ 106 | or '210100' in getline or '001012' in getline\ 107 | or '*10100' in getline or '00101*' in getline\ 108 | or '210010' in getline or '010012' in getline\ 109 | or '*10010' in getline or '01001*' in getline\ 110 | or '10001' in getline or '2010102' in getline\ 111 | or '*01010*' in getline or '201010*' in getline\ 112 | or '*010102' in getline or '2011002' in getline\ 113 | or '2001102' in getline or '*011002' in getline\ 114 | or '200110*' in getline or '201100*' in getline\ 115 | or '*001102' in getline: 116 | return 'sleep2' 117 | if '010' in getline: 118 | return 'alive1' 119 | else: 120 | return 'nothreat' 121 | 122 | # 判断对方棋型 123 | def judgeType2(getline): 124 | if '22222' in getline: 125 | return 'win5' 126 | if '022220' in getline: 127 | return 'alive4' 128 | if '122220' in getline or '022221' in getline\ 129 | or '*22220' in getline or '02222*' in getline: 130 | return 'lian-rush4' 131 | if '22202' in getline or '20222' in getline\ 132 | or '22022' in getline: 133 | return 'tiao-rush4' 134 | if '002220' in getline or '022200' in getline: 135 | return 'lian-alive3' 136 | if '022020' in getline or '020220' in getline: 137 | return 'tiao-alive3' 138 | if '122200' in getline or '002221' in getline\ 139 | or '*22200' in getline or '00222*' in getline: 140 | return 'lian-sleep3' 141 | if '122020' in getline or '020221' in getline\ 142 | or '*22020' in getline or '02022*' in getline\ 143 | or '120220' in getline or '022021' in getline\ 144 | or '*20220' in getline or '02202*' in getline: 145 | return 'tiao-sleep3' 146 | if '22002' in getline or '20022' in getline\ 147 | or '20202' in getline: 148 | return 'te-sleep3' 149 | if '1022201' in getline or '*022201' in getline\ 150 | or '102220*' in getline or '*02220*' in getline: 151 | return 'jia-alive3' 152 | if '002200' in getline or '022000' in getline\ 153 | or '000220' in getline or '002020' in getline\ 154 | or '020200' in getline or '020020' in getline: 155 | return 'alive2' 156 | if '122000' in getline or '000221' in getline\ 157 | or '*22000' in getline or '00022*' in getline\ 158 | or '120200' in getline or '002021' in getline\ 159 | or '*20200' in getline or '00202*' in getline\ 160 | or '120020' in getline or '020021' in getline\ 161 | or '*20020' in getline or '02002*' in getline\ 162 | or '20002' in getline or '1020201' in getline\ 163 | or '*02020*' in getline or '102020*' in getline\ 164 | or '*020201' in getline or '1022001' in getline\ 165 | or '1002201' in getline or '*022001' in getline\ 166 | or '100220*' in getline or '102200*' in getline\ 167 | or '*002201' in getline: 168 | return 'sleep2' 169 | if '020' in getline: 170 | return 'alive1' 171 | else: 172 | return 'nothreat' 173 | 174 | # 计算我方形势分数 175 | def evaluate_self(table): 176 | row, col = table.shape 177 | myscore = 0 178 | for i in range(row): 179 | for j in range(col): 180 | if table[i, j] == 1: 181 | point = (i, j) 182 | myType={'win5':0, 'alive4':0, 'lian-rush4':0, 'tiao-rush4':0, 'lian-alive3':0, 'tiao-alive3':0,\ 183 | 'lian-sleep3':0, 'tiao-sleep3':0, 'te-sleep3':0, 'jia-alive3':0,\ 184 | 'alive2':0, 'sleep2':0, 'alive1':0, 'nothreat':0} 185 | lines = getstring(point) 186 | for item0 in lines: 187 | tmp1 = judgeType1(item0) 188 | myType[tmp1] += 1 189 | # my score 190 | myscore += 1000000*myType['win5']+20000*myType['alive4']+ \ 191 | 6100*myType['lian-rush4']+6000*myType['tiao-rush4']+ \ 192 | 1100*myType['lian-alive3']+1000*myType['tiao-alive3']+ \ 193 | 300*myType['lian-sleep3']+290*myType['tiao-sleep3']+\ 194 | 290*myType['te-sleep3']+290*myType['jia-alive3']+\ 195 | 100*myType['alive2']+10*myType['sleep2']+\ 196 | 3*myType['alive1']+1*myType['nothreat'] 197 | return myscore 198 | 199 | # 计算敌方的形势分数 200 | def evaluate_op(table): 201 | row, col = table.shape 202 | opscore = 0 203 | for i in range(row): 204 | for j in range(col): 205 | if table[i, j] == 2: 206 | point = (i, j) 207 | opType = {'win5':0, 'alive4':0, 'lian-rush4':0, 'tiao-rush4':0, 'lian-alive3':0, 'tiao-alive3':0,\ 208 | 'lian-sleep3':0, 'tiao-sleep3':0, 'te-sleep3':0, 'jia-alive3':0,\ 209 | 'alive2':0, 'sleep2':0, 'alive1':0, 'nothreat':0} 210 | lines = getstring(point) 211 | for item0 in lines: 212 | tmp2 = judgeType2(item0) 213 | opType[tmp2] += 1 214 | # opponent score 215 | opscore += 1000000*opType['win5']+100000*opType['alive4']+ \ 216 | 65000*opType['lian-rush4']+65000*opType['tiao-rush4']+ \ 217 | 5500*opType['lian-alive3']+5000*opType['tiao-alive3']+ \ 218 | 200*opType['lian-sleep3']+200*opType['tiao-sleep3']+\ 219 | 200*opType['te-sleep3']+200*opType['jia-alive3']+\ 220 | 90*opType['alive2']+9*opType['sleep2']+\ 221 | 4*opType['alive1']+1*opType['nothreat'] 222 | return opscore 223 | 224 | 225 | #随机返回一个score最大的位置 226 | def randomChoose(scoretable): 227 | maxValue = max(scoretable.items(), key=lambda x: x[1])[1] 228 | positions=[] 229 | for item in scoretable.items(): 230 | if item[1]==maxValue: 231 | positions.append(item[0]) 232 | return choice(positions) 233 | 234 | 235 | if len(board[0]) == 0 and len(board[1]) == 0: 236 | return (board_size/2 + 1, board_size/2 + 1) 237 | else: 238 | # 获得局部搜索区域的下标 239 | sumBoard = board[0] | board[1] 240 | xmax = min(max(sumBoard, key=lambda x: x[0])[0] + 2, 15) 241 | ymax = min(max(sumBoard, key=lambda x: x[1])[1] + 2, 15) 242 | xmin = max(min(sumBoard, key=lambda x: x[0])[0] - 3, 0) 243 | ymin = max(min(sumBoard, key=lambda x: x[1])[1] - 3, 0) 244 | 245 | scoretable={} 246 | for i in range(xmin, xmax): 247 | for j in range(ymin, ymax): 248 | if table[i, j] == 0: 249 | #old = evaluate_self(table)-defend*evaluate_op(table) 250 | table[i, j] = 1 251 | scoretable[(i, j)] = evaluate_self(table)-evaluate_op(table) 252 | table[i, j] = 0 253 | self_position = randomChoose(scoretable) 254 | return (self_position[0]+1, self_position[1]+1) 255 | 256 | 257 | def finish(): 258 | pass 259 | -------------------------------------------------------------------------------- /app/static/js/jquery-weui.js: -------------------------------------------------------------------------------- 1 | /* global $:true */ 2 | /* global WebKitCSSMatrix:true */ 3 | 4 | (function($) { 5 | "use strict"; 6 | 7 | $.fn.transitionEnd = function(callback) { 8 | var events = ['webkitTransitionEnd', 'transitionend', 'oTransitionEnd', 'MSTransitionEnd', 'msTransitionEnd'], 9 | i, dom = this; 10 | 11 | function fireCallBack(e) { 12 | /*jshint validthis:true */ 13 | if (e.target !== this) return; 14 | callback.call(this, e); 15 | for (i = 0; i < events.length; i++) { 16 | dom.off(events[i], fireCallBack); 17 | } 18 | } 19 | if (callback) { 20 | for (i = 0; i < events.length; i++) { 21 | dom.on(events[i], fireCallBack); 22 | } 23 | } 24 | return this; 25 | }; 26 | 27 | $.support = (function() { 28 | var support = { 29 | touch: !!(('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch) 30 | }; 31 | return support; 32 | })(); 33 | 34 | $.touchEvents = { 35 | start: $.support.touch ? 'touchstart' : 'mousedown', 36 | move: $.support.touch ? 'touchmove' : 'mousemove', 37 | end: $.support.touch ? 'touchend' : 'mouseup' 38 | }; 39 | 40 | $.getTouchPosition = function(e) { 41 | e = e.originalEvent || e; //jquery wrap the originevent 42 | if(e.type === 'touchstart' || e.type === 'touchmove' || e.type === 'touchend') { 43 | return { 44 | x: e.targetTouches[0].pageX, 45 | y: e.targetTouches[0].pageY 46 | }; 47 | } else { 48 | return { 49 | x: e.pageX, 50 | y: e.pageY 51 | }; 52 | } 53 | }; 54 | 55 | $.fn.scrollHeight = function() { 56 | return this[0].scrollHeight; 57 | }; 58 | })($); 59 | 60 | + function($) { 61 | "use strict"; 62 | 63 | var defaults; 64 | 65 | $.modal = function(params) { 66 | params = $.extend({}, defaults, params); 67 | 68 | var mask = $("
").appendTo(document.body); 69 | 70 | var buttons = params.buttons; 71 | 72 | var buttonsHtml = buttons.map(function(d, i) { 73 | return '' + d.text + ''; 74 | }).join(""); 75 | 76 | var tpl = '
' + 77 | '
' + params.title + '
' + 78 | ( params.text ? '
'+params.text+'
' : '')+ 79 | '
' + buttonsHtml + '
' + 80 | '
'; 81 | var dialog = $(tpl).appendTo(document.body); 82 | 83 | dialog.find(".weui_btn_dialog").each(function(i, e) { 84 | var el = $(e); 85 | el.click(function() { 86 | //先关闭对话框,再调用回调函数 87 | $.closeModal(); 88 | if(buttons[i].onClick) { 89 | buttons[i].onClick(); 90 | } 91 | }); 92 | }); 93 | 94 | mask.show(); 95 | dialog.show(); 96 | mask.addClass("weui_mask_visible"); 97 | dialog.addClass("weui_dialog_visible"); 98 | }; 99 | 100 | $.closeModal = function() { 101 | $(".weui_mask_visible").removeClass("weui_mask_visible").transitionEnd(function() { 102 | $(this).remove(); 103 | }); 104 | $(".weui_dialog_visible").removeClass("weui_dialog_visible").transitionEnd(function() { 105 | $(this).remove(); 106 | }); 107 | }; 108 | 109 | $.alert = function(text, title, callback) { 110 | if (typeof title === 'function') { 111 | callback = arguments[1]; 112 | title = undefined; 113 | } 114 | return $.modal({ 115 | text: text, 116 | title: title, 117 | buttons: [{ 118 | text: defaults.buttonOK, 119 | className: "primary", 120 | onClick: callback 121 | }] 122 | }); 123 | } 124 | 125 | $.confirm = function(text, title, callbackOK, callbackCancel) { 126 | if (typeof title === 'function') { 127 | callbackCancel = arguments[2]; 128 | callbackOK = arguments[1]; 129 | title = undefined; 130 | } 131 | return $.modal({ 132 | text: text, 133 | title: title, 134 | buttons: [ 135 | { 136 | text: defaults.buttonCancel, 137 | className: "default", 138 | onClick: callbackCancel 139 | }, 140 | { 141 | text: defaults.buttonOK, 142 | className: "primary", 143 | onClick: callbackOK 144 | }] 145 | }); 146 | }; 147 | 148 | defaults = $.modal.prototype.defaults = { 149 | title: "提示", 150 | text: undefined, 151 | buttonOK: "确定", 152 | buttonCancel: "取消", 153 | buttons: [{ 154 | text: "确定", 155 | className: "primary" 156 | }] 157 | }; 158 | 159 | }($); 160 | 161 | + function($) { 162 | "use strict"; 163 | 164 | var defaults; 165 | 166 | var show = function(html, className) { 167 | 168 | var mask = $("
").appendTo(document.body); 169 | 170 | var tpl = '
' + html + '
'; 171 | var dialog = $(tpl).appendTo(document.body); 172 | 173 | dialog.show(); 174 | dialog.addClass("weui_toast_visible"); 175 | }; 176 | 177 | var hide = function() { 178 | $(".weui_mask_transparent").hide(); 179 | $(".weui_toast_visible").removeClass("weui_toast_visible").transitionEnd(function() { 180 | $(this).remove(); 181 | }); 182 | } 183 | 184 | $.toast = function(text) { 185 | show('

' + (text || "已经完成") + '

'); 186 | 187 | setTimeout(function() { 188 | hide(); 189 | }, toastDefaults.duration); 190 | } 191 | 192 | $.showLoading = function(text) { 193 | var html = '
'; 194 | for(var i=0;i<12;i++) { 195 | html += '
'; 196 | } 197 | html += '
'; 198 | html += '

' + (text || "数据加载中") + '

'; 199 | show(html, 'weui_loading_toast'); 200 | } 201 | 202 | $.hideLoading = function() { 203 | hide(); 204 | } 205 | 206 | var toastDefaults = $.toast.prototype.defaults = { 207 | duration: 2000 208 | } 209 | 210 | }($); 211 | 212 | + function($) { 213 | "use strict"; 214 | 215 | var defaults; 216 | 217 | var show = function(params) { 218 | 219 | var mask = $("
").appendTo(document.body); 220 | 221 | var actions = params.actions || []; 222 | 223 | var actionsHtml = actions.map(function(d, i) { 224 | return '
' + d.text + '
'; 225 | }).join(""); 226 | 227 | var tpl = '
'+ 228 | '
'+ 229 | actionsHtml + 230 | '
'+ 231 | '
'+ 232 | '
取消
'+ 233 | '
'+ 234 | '
'; 235 | var dialog = $(tpl).appendTo(document.body); 236 | 237 | dialog.find(".weui_actionsheet_menu .weui_actionsheet_cell, .weui_actionsheet_action .weui_actionsheet_cell").each(function(i, e) { 238 | $(e).click(function() { 239 | $.closeActions(); 240 | if(actions[i].onClick) { 241 | actions[i].onClick(); 242 | } 243 | }) 244 | }); 245 | 246 | mask.show(); 247 | dialog.show(); 248 | mask.addClass("weui_mask_visible"); 249 | dialog.addClass("weui_actionsheet_toggle"); 250 | }; 251 | 252 | var hide = function() { 253 | $(".weui_mask").removeClass("weui_mask_visible").transitionEnd(function() { 254 | $(this).remove(); 255 | }); 256 | $(".weui_actionsheet").removeClass("weui_actionsheet_toggle").transitionEnd(function() { 257 | $(this).remove(); 258 | }); 259 | } 260 | 261 | $.actions = function(params) { 262 | params = $.extend({}, defaults, params); 263 | show(params); 264 | } 265 | 266 | $.closeActions = function() { 267 | hide(); 268 | } 269 | 270 | var defaults = $.actions.prototype.defaults = { 271 | /*actions: [{ 272 | text: "菜单", 273 | className: "danger", 274 | onClick: function() { 275 | console.log(1); 276 | } 277 | },{ 278 | text: "菜单2", 279 | className: "danger", 280 | onClick: function() { 281 | console.log(2); 282 | } 283 | }]*/ 284 | } 285 | 286 | }($); 287 | 288 | /* =============================================================================== 289 | ************ Notification ************ 290 | =============================================================================== */ 291 | /* global $:true */ 292 | +function ($) { 293 | "use strict"; 294 | 295 | var distance = 50; 296 | var container, start, diffX, diffY; 297 | 298 | var touchStart = function(e) { 299 | if(container.hasClass("refreshing")) return; 300 | var p = $.getTouchPosition(e); 301 | start = p; 302 | diffX = diffY = 0; 303 | }; 304 | var touchMove = function(e) { 305 | if(container.hasClass("refreshing")) return; 306 | if(!start) return false; 307 | if(container.scrollTop() > 0) return; 308 | var p = $.getTouchPosition(e); 309 | diffX = p.x - start.x; 310 | diffY = p.y - start.y; 311 | if(diffY < 0) return; 312 | container.addClass("touching"); 313 | e.preventDefault(); 314 | e.stopPropagation(); 315 | diffY = Math.pow(diffY, 0.8); 316 | container.css("transform", "translate3d(0, "+diffY+"px, 0)"); 317 | 318 | if(diffY < distance) { 319 | container.removeClass("pull-up").addClass("pull-down"); 320 | } else { 321 | container.removeClass("pull-down").addClass("pull-up"); 322 | } 323 | }; 324 | var touchEnd = function() { 325 | start = false; 326 | if(diffY <= 0 || container.hasClass("refreshing")) return; 327 | container.removeClass("touching"); 328 | container.removeClass("pull-down pull-up"); 329 | container.css("transform", ""); 330 | if(Math.abs(diffY) <= distance) { 331 | } else { 332 | container.addClass("refreshing"); 333 | container.trigger("pull-to-refresh"); 334 | } 335 | 336 | 337 | }; 338 | 339 | var attachEvents = function(el) { 340 | el = $(el); 341 | el.addClass("weui-pull-to-refresh"); 342 | container = el; 343 | el.on($.touchEvents.start, touchStart); 344 | el.on($.touchEvents.move, touchMove); 345 | el.on($.touchEvents.end, touchEnd); 346 | }; 347 | 348 | var pullToRefresh = function(el) { 349 | attachEvents(el); 350 | }; 351 | 352 | var pullToRefreshDone = function(el) { 353 | $(el).removeClass("refreshing"); 354 | } 355 | 356 | $.fn.pullToRefresh = function() { 357 | return this.each(function() { 358 | pullToRefresh(this); 359 | }); 360 | } 361 | 362 | $.fn.pullToRefreshDone = function() { 363 | return this.each(function() { 364 | pullToRefreshDone(this); 365 | }); 366 | } 367 | 368 | }($); 369 | 370 | /* =============================================================================== 371 | ************ Notification ************ 372 | =============================================================================== */ 373 | /* global $:true */ 374 | +function ($) { 375 | "use strict"; 376 | 377 | var distance = 50; 378 | var container; 379 | 380 | var scroll = function() { 381 | var offset = container.scrollHeight() - ($(window).height() + container.scrollTop()); 382 | if(offset <= distance) { 383 | container.trigger("infinite"); 384 | } 385 | } 386 | 387 | var attachEvents = function(el, off) { 388 | el = $(el); 389 | container = el; 390 | var scrollContainer = (el[0].tagName.toUpperCase() === "BODY" ? $(document) : el); 391 | scrollContainer[off ? "off" : "on"]("scroll", scroll); 392 | }; 393 | 394 | var infinite = function(el) { 395 | attachEvents(el); 396 | } 397 | 398 | var infinite = function(el) { 399 | attachEvents(el); 400 | } 401 | 402 | $.fn.infinite = function() { 403 | return this.each(function() { 404 | infinite(this); 405 | }); 406 | } 407 | $.fn.destroyInfinite = function() { 408 | return this.each(function() { 409 | attachEvents(this, true); 410 | }); 411 | } 412 | 413 | }($); 414 | -------------------------------------------------------------------------------- /genetic_algo/genetic_AI.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import numpy as np 3 | from random import choice 4 | 5 | def strategy(state, score): 6 | # score is a global variable 7 | # score is a 28-dimension array 8 | # initial: [1000000, 20000, 6100, 6000, 1100, 1000, 300, 290, 290, 290, 100, 10, 3, 1, 9 | # 1000000, 100000, 65000, 65000, 5500, 5000, 200, 200, 200, 200, 90, 9, 4, 1] 10 | """ Information provided to you: 11 | state = (board, last_move, playing, board_size) 12 | board = (x_stones, o_stones) 13 | stones is a set contains positions of one player's stones. e.g. 14 | x_stones = {(8,8), (8,9), (8,10), (8,11)} 15 | playing = 0|1, the current player's index 16 | 17 | Your strategy will return a position code for the next stone, e.g. (8,7) 18 | """ 19 | board, last_move, playing, board_size = state 20 | row = board_size 21 | col = board_size 22 | # create a table to record the board state 23 | # 1: occupied by self 24 | # 2: occupied by opponent 25 | # 0: available 26 | table = np.zeros([row, col]) 27 | for i in range(row): 28 | for j in range(col): 29 | if playing == 1: 30 | if (i+1, j+1) in board[0]: 31 | table[i, j] = 2 32 | elif (i+1, j+1) in board[1]: 33 | table[i, j] = 1 34 | else: 35 | if (i+1, j+1) in board[0]: 36 | table[i, j] = 1 37 | elif (i+1, j+1) in board[1]: 38 | table[i, j] = 2 39 | # 获取该点4个方向的棋型 40 | def getstring(point): 41 | x = point[0] 42 | y = point[1] 43 | # vertical 44 | getLine1 = '' 45 | for k in range(max(x - 4, 0), min(x + 5, 15)): 46 | getLine1 += str(int(table[k, y])) 47 | if x-4<0: getLine1 = '*'+getLine1 48 | if x+4>14: getLine1 = getLine1+'*' 49 | # horizonal 50 | getLine2 = '' 51 | for k in range(max(y - 4, 0), min(y + 5, 15)): 52 | getLine2 += str(int(table[x, k])) 53 | if y-4<0: getLine2 = '*'+getLine2 54 | if y+4>14: getLine2 = getLine2+'*' 55 | # Oblique 45 56 | getLine3 = '' 57 | bx = max(0, x - 4) 58 | by = max(0, y - 4) 59 | ux = min(14, x + 4) 60 | uy = min(14, y + 4) 61 | for k in range(max(bx - x, by - y), min(ux - x, uy - y)+1): 62 | getLine3 += str(int(table[x + k, y + k])) 63 | if x-4<0 or y-4<0: getLine3 = '*'+getLine3 64 | if x+4>14 or y+4>14: getLine3 = getLine3+'*' 65 | # Oblique 135 66 | getLine4 = '' 67 | for k in range(max(bx - x, y - uy), min(ux - x, y - by)+1): 68 | getLine4 += str(int(table[x + k, y - k])) 69 | if x-4<0 or y+4>14: getLine4 = '*'+getLine4 70 | if x+4>14 or y-4<0: getLine4 = getLine4+'*' 71 | 72 | return [getLine1, getLine2, getLine3, getLine4] 73 | 74 | # 判断我方棋型 75 | def judgeType1(getline): 76 | if '11111' in getline: 77 | return 'win5' 78 | if '011110' in getline: 79 | return 'alive4' 80 | if '211110' in getline or '011112' in getline\ 81 | or '*11110' in getline or '01111*' in getline: 82 | return 'lian-rush4' 83 | if '11101' in getline or '10111' in getline\ 84 | or '11011' in getline: 85 | return 'tiao-rush4' 86 | if '001110' in getline or '011100' in getline: 87 | return 'lian-alive3' 88 | if '011010' in getline or '010110' in getline: 89 | return 'tiao-alive3' 90 | if '211100' in getline or '001112' in getline\ 91 | or '*11100' in getline or '00111*' in getline: 92 | return 'lian-sleep3' 93 | if '211010' in getline or '010112' in getline\ 94 | or '*11010' in getline or '01011*' in getline\ 95 | or '210110' in getline or '011012' in getline\ 96 | or '*10110' in getline or '01101*' in getline: 97 | return 'tiao-sleep3' 98 | if '11001' in getline or '10011' in getline\ 99 | or '10101' in getline: 100 | return 'te-sleep3' 101 | if '2011102' in getline or '*011102' in getline\ 102 | or '201110*' in getline or '*01110*' in getline: 103 | return 'jia-alive3' 104 | if '001100' in getline or '011000' in getline\ 105 | or '000110' in getline or '001010' in getline\ 106 | or '010100' in getline or '010010' in getline: 107 | return 'alive2' 108 | if '211000' in getline or '000112' in getline\ 109 | or '*11000' in getline or '00011*' in getline\ 110 | or '210100' in getline or '001012' in getline\ 111 | or '*10100' in getline or '00101*' in getline\ 112 | or '210010' in getline or '010012' in getline\ 113 | or '*10010' in getline or '01001*' in getline\ 114 | or '10001' in getline or '2010102' in getline\ 115 | or '*01010*' in getline or '201010*' in getline\ 116 | or '*010102' in getline or '2011002' in getline\ 117 | or '2001102' in getline or '*011002' in getline\ 118 | or '200110*' in getline or '201100*' in getline\ 119 | or '*001102' in getline: 120 | return 'sleep2' 121 | if '010' in getline: 122 | return 'alive1' 123 | else: 124 | return 'nothreat' 125 | 126 | # 判断对方棋型 127 | def judgeType2(getline): 128 | if '22222' in getline: 129 | return 'win5' 130 | if '022220' in getline: 131 | return 'alive4' 132 | if '122220' in getline or '022221' in getline\ 133 | or '*22220' in getline or '02222*' in getline: 134 | return 'lian-rush4' 135 | if '22202' in getline or '20222' in getline\ 136 | or '22022' in getline: 137 | return 'tiao-rush4' 138 | if '002220' in getline or '022200' in getline: 139 | return 'lian-alive3' 140 | if '022020' in getline or '020220' in getline: 141 | return 'tiao-alive3' 142 | if '122200' in getline or '002221' in getline\ 143 | or '*22200' in getline or '00222*' in getline: 144 | return 'lian-sleep3' 145 | if '122020' in getline or '020221' in getline\ 146 | or '*22020' in getline or '02022*' in getline\ 147 | or '120220' in getline or '022021' in getline\ 148 | or '*20220' in getline or '02202*' in getline: 149 | return 'tiao-sleep3' 150 | if '22002' in getline or '20022' in getline\ 151 | or '20202' in getline: 152 | return 'te-sleep3' 153 | if '1022201' in getline or '*022201' in getline\ 154 | or '102220*' in getline or '*02220*' in getline: 155 | return 'jia-alive3' 156 | if '002200' in getline or '022000' in getline\ 157 | or '000220' in getline or '002020' in getline\ 158 | or '020200' in getline or '020020' in getline: 159 | return 'alive2' 160 | if '122000' in getline or '000221' in getline\ 161 | or '*22000' in getline or '00022*' in getline\ 162 | or '120200' in getline or '002021' in getline\ 163 | or '*20200' in getline or '00202*' in getline\ 164 | or '120020' in getline or '020021' in getline\ 165 | or '*20020' in getline or '02002*' in getline\ 166 | or '20002' in getline or '1020201' in getline\ 167 | or '*02020*' in getline or '102020*' in getline\ 168 | or '*020201' in getline or '1022001' in getline\ 169 | or '1002201' in getline or '*022001' in getline\ 170 | or '100220*' in getline or '102200*' in getline\ 171 | or '*002201' in getline: 172 | return 'sleep2' 173 | if '020' in getline: 174 | return 'alive1' 175 | else: 176 | return 'nothreat' 177 | 178 | # 计算我方形势分数 179 | def evaluate_self(table): 180 | row, col = table.shape 181 | myscore = 0 182 | for i in range(row): 183 | for j in range(col): 184 | if table[i, j] == 1: 185 | point = (i, j) 186 | myType={'win5':0, 'alive4':0, 'lian-rush4':0, 'tiao-rush4':0, 'lian-alive3':0, 'tiao-alive3':0,\ 187 | 'lian-sleep3':0, 'tiao-sleep3':0, 'te-sleep3':0, 'jia-alive3':0,\ 188 | 'alive2':0, 'sleep2':0, 'alive1':0, 'nothreat':0} 189 | lines = getstring(point) 190 | for item0 in lines: 191 | tmp1 = judgeType1(item0) 192 | myType[tmp1] += 1 193 | # my score 194 | myscore += score[0]*myType['win5']+score[1]*myType['alive4']+ \ 195 | score[2]*myType['lian-rush4']+score[3]*myType['tiao-rush4']+ \ 196 | score[4]*myType['lian-alive3']+score[5]*myType['tiao-alive3']+ \ 197 | score[6]*myType['lian-sleep3']+score[7]*myType['tiao-sleep3']+\ 198 | score[8]*myType['te-sleep3']+score[9]*myType['jia-alive3']+\ 199 | score[10]*myType['alive2']+score[11]*myType['sleep2']+\ 200 | score[12]*myType['alive1']+score[13]*myType['nothreat'] 201 | return myscore 202 | 203 | # 计算敌方的形势分数 204 | def evaluate_op(table): 205 | row, col = table.shape 206 | opscore = 0 207 | for i in range(row): 208 | for j in range(col): 209 | if table[i, j] == 2: 210 | point = (i, j) 211 | opType = {'win5':0, 'alive4':0, 'lian-rush4':0, 'tiao-rush4':0, 'lian-alive3':0, 'tiao-alive3':0,\ 212 | 'lian-sleep3':0, 'tiao-sleep3':0, 'te-sleep3':0, 'jia-alive3':0,\ 213 | 'alive2':0, 'sleep2':0, 'alive1':0, 'nothreat':0} 214 | lines = getstring(point) 215 | for item0 in lines: 216 | tmp2 = judgeType2(item0) 217 | opType[tmp2] += 1 218 | # opponent score 219 | opscore += score[14]*opType['win5']+score[15]*opType['alive4']+ \ 220 | score[16]*opType['lian-rush4']+score[17]*opType['tiao-rush4']+ \ 221 | score[18]*opType['lian-alive3']+score[19]*opType['tiao-alive3']+ \ 222 | score[20]*opType['lian-sleep3']+score[21]*opType['tiao-sleep3']+\ 223 | score[22]*opType['te-sleep3']+score[23]*opType['jia-alive3']+\ 224 | score[24]*opType['alive2']+score[25]*opType['sleep2']+\ 225 | score[26]*opType['alive1']+score[27]*opType['nothreat'] 226 | return opscore 227 | 228 | 229 | #随机返回一个score最大的位置 230 | def randomChoose(scoretable): 231 | maxValue = max(scoretable.items(), key=lambda x: x[1])[1] 232 | positions=[] 233 | for item in scoretable.items(): 234 | if item[1]==maxValue: 235 | positions.append(item[0]) 236 | return choice(positions) 237 | 238 | 239 | if len(board[0]) == 0 and len(board[1]) == 0: 240 | return (board_size/2 + 1, board_size/2 + 1) 241 | else: 242 | # 获得局部搜索区域的下标 243 | sumBoard = board[0] | board[1] 244 | xmax = min(max(sumBoard, key=lambda x: x[0])[0] + 2, 15) 245 | ymax = min(max(sumBoard, key=lambda x: x[1])[1] + 2, 15) 246 | xmin = max(min(sumBoard, key=lambda x: x[0])[0] - 3, 0) 247 | ymin = max(min(sumBoard, key=lambda x: x[1])[1] - 3, 0) 248 | 249 | scoretable={} 250 | for i in range(xmin, xmax): 251 | for j in range(ymin, ymax): 252 | if table[i, j] == 0: 253 | #old = evaluate_self(table)-defend*evaluate_op(table) 254 | table[i, j] = 1 255 | scoretable[(i, j)] = evaluate_self(table)-evaluate_op(table) 256 | table[i, j] = 0 257 | self_position = randomChoose(scoretable) 258 | return (self_position[0]+1, self_position[1]+1) 259 | 260 | -------------------------------------------------------------------------------- /app/gomoku.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -- coding: utf-8 -- 3 | from __future__ import print_function, division 4 | import os, sys, time, collections 5 | from functools import update_wrapper 6 | import pickle 7 | 8 | def decorator(d): 9 | "Make function d a decorator: d wraps a function fn." 10 | def _d(fn): 11 | return update_wrapper(d(fn), fn) 12 | update_wrapper(_d, d) 13 | return _d 14 | 15 | @decorator 16 | def memo(f): 17 | """Decorator that caches the return value for each call to f(args). 18 | Then when called again with same args, we can just look it up.""" 19 | cache = {} 20 | def _f(*args): 21 | try: 22 | return cache[args] 23 | except KeyError: 24 | cache[args] = result = f(*args) 25 | return result 26 | except TypeError: 27 | # some element of args refuses to be a dict key 28 | return f(args) 29 | _f.cache = cache 30 | return _f 31 | 32 | @memo 33 | def colored(s, color=''): 34 | if color.lower() == 'green': 35 | return '\033[92m' + s + '\033[0m' 36 | elif color.lower() == 'yellow': 37 | return '\033[93m' + s + '\033[0m' 38 | elif color.lower() == 'red': 39 | return '\033[91m' + s + '\033[0m' 40 | elif color.lower() == 'blue': 41 | return '\033[94m' + s + '\033[0m' 42 | elif color.lower() == 'bold': 43 | return '\033[1m' + s + '\033[0m' 44 | else: 45 | return s 46 | 47 | class Gomoku(object): 48 | """ Gomoku Game Rules: 49 | Two players alternatively put their stone on the board. First one got five in a row wins. 50 | """ 51 | 52 | def __init__(self, board_size=15, players=None, fastmode=False, first_center=None, silent_mode=False, winning_num=5): 53 | self.reset() 54 | self.board_size = board_size 55 | self.fastmode = fastmode 56 | self.playing = None 57 | self.players = [Player(player_name) for player_name in players] 58 | self.winning_stones = set() 59 | self.last_move = None 60 | self.first_center = first_center 61 | self.silent_mode = silent_mode 62 | self.winning_num = winning_num 63 | 64 | @property 65 | def state(self): 66 | return (self.board, self.last_move, self.playing, self.board_size) 67 | 68 | def load_state(self, state): 69 | (self.board, self.last_move, self.playing, self.board_size) = state 70 | 71 | def reset(self): 72 | self.board = (set(), set()) 73 | 74 | def print_board(self): 75 | print(' '*4 + ' '.join([chr(97+i) for i in range(self.board_size)])) 76 | print(' '*3 + '='*(2*self.board_size)) 77 | for x in range(1, self.board_size+1): 78 | row = ['%2s|'%x] 79 | for y in range(1, self.board_size+1): 80 | if (x,y) in self.board[0]: 81 | c = 'x' 82 | elif (x,y) in self.board[1]: 83 | c = 'o' 84 | else: 85 | c = '-' 86 | if (x,y) in self.winning_stones or (x,y) == self.last_move: 87 | c = colored(c, 'green') 88 | row.append(c) 89 | print(' '.join(row)) 90 | 91 | def play(self): 92 | if self.fastmode < 2: print("Game Start!") 93 | i_turn = len(self.board[0]) + len(self.board[1]) 94 | new_step = None 95 | while True: 96 | if self.fastmode < 2: print("----- Turn %d -------" % i_turn) 97 | self.playing = i_turn % 2 98 | if self.fastmode < 2 and not self.silent_mode: 99 | self.print_board() 100 | current_player = self.players[self.playing] 101 | other_player = self.players[int(not self.playing)] 102 | if self.fastmode < 2: print("--- %s's turn ---" % current_player.name) 103 | max_try = 5 104 | for i_try in range(max_try): 105 | action = current_player.strategy(self.state) 106 | if action == (0, 0): 107 | print("Player %s admit defeat!" % current_player.name) 108 | winner = other_player.name 109 | if self.fastmode < 2: print("Winner is %s"%winner) 110 | return winner 111 | self.last_move = action 112 | if self.place_stone() is True: 113 | break 114 | if i_try == max_try-1: 115 | print("Player %s has made %d illegal moves, he lost."%(current_player.name, max_try)) 116 | winner = other_player.name 117 | print("Winner is %s"%winner) 118 | return winner 119 | # check if current player wins 120 | winner = self.check_winner() 121 | if winner: 122 | if not self.silent_mode: 123 | self.print_board() 124 | print("########## %s is the WINNER! #########" % current_player.name) 125 | return winner 126 | elif i_turn == self.board_size ** 2 - 1: 127 | print("This game is a tie!") 128 | return "Tie" 129 | i_turn += 1 130 | 131 | def place_stone(self): 132 | # check if this position is on the board 133 | r, c = self.last_move 134 | if r < 1 or r > self.board_size or c < 1 or c > self.board_size: 135 | print("This position is outside the board!") 136 | return False 137 | # check if this position is already taken 138 | taken_pos = self.board[0] | self.board[1] 139 | if self.first_center is True and len(taken_pos) == 0: 140 | # if this is the very first move, it must be on the center 141 | center = int((self.board_size+1)/2) 142 | if r != center or c != center: 143 | print("This is the first move, please put it on the center (%s%s)!"% (str(center),chr(center+96))) 144 | return False 145 | elif self.last_move in taken_pos: 146 | print("This position is already taken!") 147 | return False 148 | self.board[self.playing].add(self.last_move) 149 | return True 150 | 151 | def check_winner(self): 152 | r, c = self.last_move 153 | my_stones = self.board[self.playing] 154 | # find any nearby stone 155 | nearby_stones = set() 156 | for x in range(max(r-1, 1), min(r+2, self.board_size+1)): 157 | for y in range(max(c-1, 1), min(c+2, self.board_size+1)): 158 | stone = (x,y) 159 | if stone in my_stones and (2*r-x, 2*c-y) not in nearby_stones: 160 | nearby_stones.add(stone) 161 | for nearby_s in nearby_stones: 162 | winning_stones = {self.last_move, nearby_s} 163 | nr, nc = nearby_s 164 | dx, dy = nr-r, nc-c 165 | # try to extend in this direction 166 | for i in range(1,4): 167 | ext_stone = (nr+dx*i, nc+dy*i) 168 | if ext_stone in my_stones: 169 | winning_stones.add(ext_stone) 170 | else: 171 | break 172 | # try to extend in the opposite direction 173 | for i in range(1,5): 174 | ext_stone = (r-dx*i, c-dy*i) 175 | if ext_stone in my_stones: 176 | winning_stones.add(ext_stone) 177 | else: 178 | break 179 | if len(winning_stones) >= self.winning_num: 180 | self.winning_stones = winning_stones 181 | return self.players[self.playing].name 182 | return None 183 | 184 | def delay(self, n): 185 | """ Delay n seconds if not in fastmode""" 186 | if not self.fastmode: 187 | time.sleep(n) 188 | 189 | def get_strategy(self, p): 190 | return p.strategy(self.state) 191 | 192 | class Player(object): 193 | @property 194 | def name(self): 195 | return self._name 196 | 197 | @name.setter 198 | def name(self, value): 199 | try: 200 | self._name = str(value) 201 | except: 202 | raise TypeError("Player Name must be a string.") 203 | 204 | def __repr__(self): 205 | return "Player %s"%self.name 206 | 207 | def __init__(self, name): 208 | self.name = name 209 | # Allow name to be appended by a number 210 | if (not name[0].isdigit()) and (name[-1].isdigit()): 211 | name = name[:-1] 212 | # search for the strategy file 213 | for f in os.listdir('.'): 214 | filename, fileext = os.path.splitext(f) 215 | if name.lower() == filename.lower() and fileext == '.py': 216 | print('strategy() found in %s, will use as AI.'%f) 217 | p = __import__(filename) 218 | try: 219 | self.strategy = p.strategy 220 | except: 221 | raise RuntimeError("Function strategy(state) is not found in %s"%filename) 222 | try: 223 | self.finish = p.finish 224 | except: 225 | pass 226 | # if not found, use manual input 227 | if not hasattr(self, 'strategy'): 228 | self.strategy = self.human_input 229 | 230 | def human_input(self, state): 231 | """ Ask player to place stone """ 232 | r, c = 0, 0 233 | for t in range(3): 234 | try: 235 | s = raw_input('Please place stone, enter code like "8h": ') 236 | if s == 'save': 237 | pickle.dump(state, open('saved.state','wb')) 238 | print("Current game state saved to saved.state!") 239 | continue 240 | if any(phrase in s for phrase in ['giveup','throw','admit']): 241 | break 242 | r, c = s[:-1], s[-1] 243 | r = int(r) 244 | c = ord(c) - 96 245 | break 246 | except: 247 | print("Invalid input! Please try again. (%d)"%(3-t)) 248 | pass 249 | return (r,c) 250 | 251 | 252 | def main(): 253 | import argparse 254 | parser = argparse.ArgumentParser("Play the Gomoku Game!", formatter_class=argparse.ArgumentDefaultsHelpFormatter) 255 | parser.add_argument('players', nargs='*', default=['AI', 'Player1'], help='Names of Players, the first one plays first.') 256 | parser.add_argument('--board_size', type=int, default=15, help='Size of the board.') 257 | parser.add_argument('--first_center', action='store_true', help='The first move must be on the center of board?') 258 | parser.add_argument('--fast', action='store_true', help='Run the game in fast mode.') 259 | parser.add_argument('-n', '--ngames', type=int, help='Play a number of games to gather statistics.') 260 | parser.add_argument('--fixorder', action='store_true', help='Fix the order of players in a multi-game series.') 261 | parser.add_argument('--load', help='Load a previously saved state to continue the game.') 262 | args = parser.parse_args() 263 | 264 | # fix the .py after player names 265 | players = [] 266 | for p in args.players: 267 | players.append(p[:-3] if p.endswith('.py') else p) 268 | assert len(players) == 2, "Gomoku can only be played with 2 players." 269 | 270 | 271 | game = Gomoku(board_size=args.board_size, players=players, fastmode=args.fast, first_center=args.first_center) 272 | if args.load: 273 | state = pickle.load(open(args.load,'rb')) 274 | game.load_state(state) 275 | 276 | if args.ngames is None: 277 | game.play() 278 | else: 279 | # check if all players have stategy function setup 280 | for p in game.players: 281 | if p.strategy.__name__ == 'human_input': 282 | print("%s need a strategy function to enter the auto-play mode. Exiting.."%p.name) 283 | return 284 | print("Gathering result of %d games..."%args.ngames) 285 | game.fastmode = 2 286 | game_output = open('game_results.txt','w') 287 | winner_board = collections.OrderedDict([(p.name, 0) for p in game.players]) 288 | winner_board["Tie"] = 0 289 | def playone(i): 290 | game_output.write('Game %-4d .'%(i+1)) 291 | game.reset() 292 | winner = game.play() 293 | winner_board[winner] += 1 294 | game_output.write('Game %-4d: Winner is %s\n'%(i+1, winner)) 295 | game_output.flush() 296 | for i in range(args.ngames): 297 | playone(i) 298 | # switch the order of the players 299 | game.players = game.players[1:] + [game.players[0]] 300 | game_output.close() 301 | print("Name | Games Won") 302 | for name, nwin in winner_board.items(): 303 | print("%-7s | %7d"%(name, nwin)) 304 | # Let the players finish their game 305 | for p in game.players: 306 | if hasattr(p, 'finish'): 307 | p.finish() 308 | 309 | if __name__ == "__main__": 310 | main() 311 | -------------------------------------------------------------------------------- /genetic_algo/gomoku.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -- coding: utf-8 -- 3 | from __future__ import print_function, division 4 | import os, sys, time, collections 5 | from functools import update_wrapper 6 | import pickle 7 | 8 | def decorator(d): 9 | "Make function d a decorator: d wraps a function fn." 10 | def _d(fn): 11 | return update_wrapper(d(fn), fn) 12 | update_wrapper(_d, d) 13 | return _d 14 | 15 | @decorator 16 | def memo(f): 17 | """Decorator that caches the return value for each call to f(args). 18 | Then when called again with same args, we can just look it up.""" 19 | cache = {} 20 | def _f(*args): 21 | try: 22 | return cache[args] 23 | except KeyError: 24 | cache[args] = result = f(*args) 25 | return result 26 | except TypeError: 27 | # some element of args refuses to be a dict key 28 | return f(args) 29 | _f.cache = cache 30 | return _f 31 | 32 | @memo 33 | def colored(s, color=''): 34 | if color.lower() == 'green': 35 | return '\033[92m' + s + '\033[0m' 36 | elif color.lower() == 'yellow': 37 | return '\033[93m' + s + '\033[0m' 38 | elif color.lower() == 'red': 39 | return '\033[91m' + s + '\033[0m' 40 | elif color.lower() == 'blue': 41 | return '\033[94m' + s + '\033[0m' 42 | elif color.lower() == 'bold': 43 | return '\033[1m' + s + '\033[0m' 44 | else: 45 | return s 46 | 47 | class Gomoku(object): 48 | """ Gomoku Game Rules: 49 | Two players alternatively put their stone on the board. First one got five in a row wins. 50 | """ 51 | 52 | def __init__(self, board_size=15, players=None, fastmode=False, first_center=None, silent_mode=False, winning_num=5): 53 | self.reset() 54 | self.board_size = board_size 55 | self.fastmode = fastmode 56 | self.playing = None 57 | self.players = [Player(player_name) for player_name in players] 58 | self.winning_stones = set() 59 | self.last_move = None 60 | self.first_center = first_center 61 | self.silent_mode = silent_mode 62 | self.winning_num = winning_num 63 | 64 | @property 65 | def state(self): 66 | return (self.board, self.last_move, self.playing, self.board_size) 67 | 68 | def load_state(self, state): 69 | (self.board, self.last_move, self.playing, self.board_size) = state 70 | 71 | def reset(self): 72 | self.board = (set(), set()) 73 | 74 | def print_board(self): 75 | print(' '*4 + ' '.join([chr(97+i) for i in range(self.board_size)])) 76 | print(' '*3 + '='*(2*self.board_size)) 77 | for x in range(1, self.board_size+1): 78 | row = ['%2s|'%x] 79 | for y in range(1, self.board_size+1): 80 | if (x,y) in self.board[0]: 81 | c = 'x' 82 | elif (x,y) in self.board[1]: 83 | c = 'o' 84 | else: 85 | c = '-' 86 | if (x,y) in self.winning_stones or (x,y) == self.last_move: 87 | c = colored(c, 'green') 88 | row.append(c) 89 | print(' '.join(row)) 90 | 91 | def play(self): 92 | if self.fastmode < 2: print("Game Start!") 93 | i_turn = len(self.board[0]) + len(self.board[1]) 94 | new_step = None 95 | while True: 96 | if self.fastmode < 2: print("----- Turn %d -------" % i_turn) 97 | self.playing = i_turn % 2 98 | if self.fastmode < 2 and not self.silent_mode: 99 | self.print_board() 100 | current_player = self.players[self.playing] 101 | other_player = self.players[int(not self.playing)] 102 | if self.fastmode < 2: print("--- %s's turn ---" % current_player.name) 103 | max_try = 5 104 | for i_try in range(max_try): 105 | action = current_player.strategy(self.state) 106 | if action == (0, 0): 107 | print("Player %s admit defeat!" % current_player.name) 108 | winner = other_player.name 109 | if self.fastmode < 2: print("Winner is %s"%winner) 110 | return winner 111 | self.last_move = action 112 | if self.place_stone() is True: 113 | break 114 | if i_try == max_try-1: 115 | print("Player %s has made %d illegal moves, he lost."%(current_player.name, max_try)) 116 | winner = other_player.name 117 | print("Winner is %s"%winner) 118 | return winner 119 | # check if current player wins 120 | winner = self.check_winner() 121 | if winner: 122 | if not self.silent_mode: 123 | self.print_board() 124 | print("########## %s is the WINNER! #########" % current_player.name) 125 | return winner 126 | elif i_turn == self.board_size ** 2 - 1: 127 | print("This game is a tie!") 128 | return "Tie" 129 | i_turn += 1 130 | 131 | def place_stone(self): 132 | # check if this position is on the board 133 | r, c = self.last_move 134 | if r < 1 or r > self.board_size or c < 1 or c > self.board_size: 135 | print("This position is outside the board!") 136 | return False 137 | # check if this position is already taken 138 | taken_pos = self.board[0] | self.board[1] 139 | if self.first_center is True and len(taken_pos) == 0: 140 | # if this is the very first move, it must be on the center 141 | center = int((self.board_size+1)/2) 142 | if r != center or c != center: 143 | print("This is the first move, please put it on the center (%s%s)!"% (str(center),chr(center+96))) 144 | return False 145 | elif self.last_move in taken_pos: 146 | print("This position is already taken!") 147 | return False 148 | self.board[self.playing].add(self.last_move) 149 | return True 150 | 151 | def check_winner(self): 152 | r, c = self.last_move 153 | my_stones = self.board[self.playing] 154 | # find any nearby stone 155 | nearby_stones = set() 156 | for x in range(max(r-1, 1), min(r+2, self.board_size+1)): 157 | for y in range(max(c-1, 1), min(c+2, self.board_size+1)): 158 | stone = (x,y) 159 | if stone in my_stones and (2*r-x, 2*c-y) not in nearby_stones: 160 | nearby_stones.add(stone) 161 | for nearby_s in nearby_stones: 162 | winning_stones = {self.last_move, nearby_s} 163 | nr, nc = nearby_s 164 | dx, dy = nr-r, nc-c 165 | # try to extend in this direction 166 | for i in range(1,4): 167 | ext_stone = (nr+dx*i, nc+dy*i) 168 | if ext_stone in my_stones: 169 | winning_stones.add(ext_stone) 170 | else: 171 | break 172 | # try to extend in the opposite direction 173 | for i in range(1,5): 174 | ext_stone = (r-dx*i, c-dy*i) 175 | if ext_stone in my_stones: 176 | winning_stones.add(ext_stone) 177 | else: 178 | break 179 | if len(winning_stones) >= self.winning_num: 180 | self.winning_stones = winning_stones 181 | return self.players[self.playing].name 182 | return None 183 | 184 | def delay(self, n): 185 | """ Delay n seconds if not in fastmode""" 186 | if not self.fastmode: 187 | time.sleep(n) 188 | 189 | def get_strategy(self, p): 190 | return p.strategy(self.state) 191 | 192 | class Player(object): 193 | @property 194 | def name(self): 195 | return self._name 196 | 197 | @name.setter 198 | def name(self, value): 199 | try: 200 | self._name = str(value) 201 | except: 202 | raise TypeError("Player Name must be a string.") 203 | 204 | def __repr__(self): 205 | return "Player %s"%self.name 206 | 207 | def __init__(self, name): 208 | self.name = name 209 | # Allow name to be appended by a number 210 | if (not name[0].isdigit()) and (name[-1].isdigit()): 211 | name = name[:-1] 212 | # search for the strategy file 213 | for f in os.listdir('.'): 214 | filename, fileext = os.path.splitext(f) 215 | if name.lower() == filename.lower() and fileext == '.py': 216 | print('strategy() found in %s, will use as AI.'%f) 217 | p = __import__(filename) 218 | try: 219 | self.strategy = p.strategy 220 | except: 221 | raise RuntimeError("Function strategy(state) is not found in %s"%filename) 222 | try: 223 | self.finish = p.finish 224 | except: 225 | pass 226 | # if not found, use manual input 227 | if not hasattr(self, 'strategy'): 228 | self.strategy = self.human_input 229 | 230 | def human_input(self, state): 231 | """ Ask player to place stone """ 232 | r, c = 0, 0 233 | for t in range(3): 234 | try: 235 | s = raw_input('Please place stone, enter code like "8h": ') 236 | if s == 'save': 237 | pickle.dump(state, open('saved.state','wb')) 238 | print("Current game state saved to saved.state!") 239 | continue 240 | if any(phrase in s for phrase in ['giveup','throw','admit']): 241 | break 242 | r, c = s[:-1], s[-1] 243 | r = int(r) 244 | c = ord(c) - 96 245 | break 246 | except: 247 | print("Invalid input! Please try again. (%d)"%(3-t)) 248 | pass 249 | return (r,c) 250 | 251 | 252 | def main(): 253 | import argparse 254 | parser = argparse.ArgumentParser("Play the Gomoku Game!", formatter_class=argparse.ArgumentDefaultsHelpFormatter) 255 | parser.add_argument('players', nargs='*', default=['AI', 'Player1'], help='Names of Players, the first one plays first.') 256 | parser.add_argument('--board_size', type=int, default=15, help='Size of the board.') 257 | parser.add_argument('--first_center', action='store_true', help='The first move must be on the center of board?') 258 | parser.add_argument('--fast', action='store_true', help='Run the game in fast mode.') 259 | parser.add_argument('-n', '--ngames', type=int, help='Play a number of games to gather statistics.') 260 | parser.add_argument('--fixorder', action='store_true', help='Fix the order of players in a multi-game series.') 261 | parser.add_argument('--load', help='Load a previously saved state to continue the game.') 262 | args = parser.parse_args() 263 | 264 | # fix the .py after player names 265 | players = [] 266 | for p in args.players: 267 | players.append(p[:-3] if p.endswith('.py') else p) 268 | assert len(players) == 2, "Gomoku can only be played with 2 players." 269 | 270 | 271 | game = Gomoku(board_size=args.board_size, players=players, fastmode=args.fast, first_center=args.first_center) 272 | if args.load: 273 | state = pickle.load(open(args.load,'rb')) 274 | game.load_state(state) 275 | 276 | if args.ngames is None: 277 | game.play() 278 | else: 279 | # check if all players have stategy function setup 280 | for p in game.players: 281 | if p.strategy.__name__ == 'human_input': 282 | print("%s need a strategy function to enter the auto-play mode. Exiting.."%p.name) 283 | return 284 | print("Gathering result of %d games..."%args.ngames) 285 | game.fastmode = 2 286 | game_output = open('game_results.txt','w') 287 | winner_board = collections.OrderedDict([(p.name, 0) for p in game.players]) 288 | winner_board["Tie"] = 0 289 | def playone(i): 290 | game_output.write('Game %-4d .'%(i+1)) 291 | game.reset() 292 | winner = game.play() 293 | winner_board[winner] += 1 294 | game_output.write('Game %-4d: Winner is %s\n'%(i+1, winner)) 295 | game_output.flush() 296 | for i in range(args.ngames): 297 | playone(i) 298 | # switch the order of the players 299 | game.players = game.players[1:] + [game.players[0]] 300 | game_output.close() 301 | print("Name | Games Won") 302 | for name, nwin in winner_board.items(): 303 | print("%-7s | %7d"%(name, nwin)) 304 | # Let the players finish their game 305 | for p in game.players: 306 | if hasattr(p, 'finish'): 307 | p.finish() 308 | 309 | if __name__ == "__main__": 310 | main() 311 | -------------------------------------------------------------------------------- /AI3.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | import copy 4 | import time 5 | from random import choice, shuffle 6 | from math import log, sqrt 7 | 8 | def strategy(state): 9 | """ Information provided to you: 10 | state = (board, last_move, playing, board_size) 11 | board = (x_stones, o_stones) 12 | stones is a set contains positions of one player's stones. e.g. 13 | x_stones = {(8,8), (8,9), (8,10), (8,11)} 14 | playing = 0|1, the current player's index 15 | 16 | Your strategy will return a position code for the next stone, e.g. (8,7) 17 | """ 18 | board, last_move, playing, board_size = state 19 | x_stones, o_stones = board 20 | if last_move == None: 21 | return (board_size/2 + 1, board_size/2 + 1) 22 | 23 | board_init = Board(width=board_size, height=board_size, n_in_row=4) 24 | board_init.init_board() 25 | 26 | for x,y in x_stones: 27 | location = [x-1,y-1] 28 | board_init.update(1, location) 29 | for x,y in o_stones: 30 | location = [x-1, y-1] 31 | board_init.update(2, location) 32 | print board_init.availables 33 | print board_init.states 34 | 35 | ai = MCTS(board_init, [2, 1], n_in_row = 4, time = 10, max_actions = 3000) 36 | x,y = ai.get_action() 37 | # print ("AI move: %d, %d"%(x+1,y+1)) 38 | return (x+1,y+1) 39 | 40 | 41 | # def get_action(board, play_turn, availables, calculation_time = 5): 42 | # if len(availables) == 1: 43 | # return availables[0] 44 | # plays = {} 45 | # wins = {} 46 | # simulations = 0 47 | # begin = time.time() 48 | # while time.time() - begin < calculation_time: 49 | # board_copy = copy.deepcopy(board) 50 | # play_turn_copy = copy.deepcopy(play_turn) 51 | # run_simulation(board_copy, play_turn_copy) 52 | # move = select_one_move() 53 | # 54 | # 55 | # def run_simulation(board, availables, play_turn): 56 | # plays = {} 57 | # wins = {} 58 | # visited_states = set() 59 | # winner = -1 60 | # expand = True 61 | # 62 | # max_actions = 1000 63 | # for t in range(1, max_actions + 1): 64 | 65 | class Board(object): 66 | """ 67 | board for game 68 | """ 69 | 70 | def __init__(self, **kwargs): 71 | self.width = int(kwargs.get('width', 7)) 72 | self.height = int(kwargs.get('height', 7)) 73 | self.states = {} # board states, key:(player, move), value: piece type 74 | self.n_in_row = int(kwargs.get('n_in_row', 5)) # need how many pieces in a row to win 75 | 76 | def init_board(self): 77 | # if self.width < self.n_in_row or self.height < self.n_in_row: 78 | # raise Exception('board width and height can not less than %d' % self.n_in_row) 79 | 80 | self.availables = list(range(self.width * self.height)) # available moves 81 | 82 | for m in self.availables: 83 | self.states[m] = -1 84 | 85 | def move_to_location(self, move): 86 | """ 87 | 3*3 board's moves like: 88 | 6 7 8 89 | 3 4 5 90 | 0 1 2 91 | and move 5's location is (1,2) 92 | """ 93 | h = move // self.width 94 | w = move % self.width 95 | return [h, w] 96 | 97 | def location_to_move(self, location): 98 | # if (len(location) != 2): 99 | # return -1 100 | h = location[0] 101 | w = location[1] 102 | move = h * self.width + w 103 | if (move not in range(self.width * self.height)): 104 | return -1 105 | return move 106 | 107 | def update(self, player, location): 108 | move = self.location_to_move(location) 109 | self.states[move] = player 110 | self.availables.remove(move) 111 | 112 | 113 | class MCTS(object): 114 | """ 115 | AI player, UCT RAVE 116 | """ 117 | 118 | def __init__(self, board, play_turn, n_in_row=4, time=5, max_actions=1000): 119 | self.board = board 120 | self.play_turn = play_turn 121 | self.calculation_time = float(time) 122 | self.max_actions = max_actions 123 | self.n_in_row = n_in_row 124 | 125 | self.player = play_turn[0] # AI is first at now 126 | self.confident = 1.96 127 | self.equivalence = 10000 # calc beta 128 | self.max_depth = 1 129 | 130 | def get_action(self): 131 | if len(self.board.availables) == 1: 132 | return self.board.availables[0] 133 | 134 | self.plays = {} # key:(player, move), value:visited times 135 | self.wins = {} # key:(player, move), value:win times 136 | self.plays_rave = {} # key:move, value:visited times 137 | self.wins_rave = {} # key:move, value:{player: win times} 138 | simulations = 0 139 | begin = time.time() 140 | while time.time() - begin < self.calculation_time: 141 | board_copy = copy.deepcopy(self.board) # simulation will change board's states, 142 | play_turn_copy = copy.deepcopy(self.play_turn) # and play turn 143 | self.run_simulation(board_copy, play_turn_copy) 144 | simulations += 1 145 | print(simulations) 146 | 147 | move = self.select_one_move() 148 | location = self.board.move_to_location(move) 149 | return location 150 | 151 | def run_simulation(self, board, play_turn): 152 | """ 153 | MCTS main process 154 | """ 155 | 156 | plays = self.plays 157 | wins = self.wins 158 | plays_rave = self.plays_rave 159 | wins_rave = self.wins_rave 160 | availables = board.availables 161 | 162 | player = self.get_player(play_turn) 163 | visited_states = set() 164 | winner = -1 165 | expand = True 166 | # Simulation 167 | for t in range(1, self.max_actions + 1): 168 | # Selection 169 | # if all moves have statistics info, choose one that have max UCB value 170 | if all(plays.get((player, move)) for move in availables): 171 | value, move = max( 172 | ((1 - sqrt(self.equivalence / (3 * plays_rave[move] + self.equivalence))) * ( 173 | wins[(player, move)] / plays[(player, move)]) + 174 | sqrt(self.equivalence / (3 * plays_rave[move] + self.equivalence)) * ( 175 | wins_rave[move][player] / plays_rave[move]) + 176 | sqrt(self.confident * log(plays_rave[move]) / plays[(player, move)]), move) 177 | for move in availables) # UCT RAVE 178 | else: 179 | # a simple strategy 180 | # prefer to choose the nearer moves without statistics, 181 | # and then the farthers. 182 | # try ro add statistics info to all moves quickly 183 | adjacents = [] 184 | if len(availables) > self.n_in_row: 185 | adjacents = self.adjacent_moves(board, player, plays) 186 | 187 | if len(adjacents): 188 | move = choice(adjacents) 189 | else: 190 | peripherals = [] 191 | for move in availables: 192 | if not plays.get((player, move)): 193 | peripherals.append(move) 194 | move = choice(peripherals) 195 | h = move // board.width 196 | w = move % board.width 197 | location = [h, w] 198 | board.update(player, location) 199 | 200 | # Expand 201 | # add only one new child node each time 202 | if expand and (player, move) not in plays: 203 | expand = False 204 | plays[(player, move)] = 0 205 | wins[(player, move)] = 0 206 | if move not in plays_rave: 207 | plays_rave[move] = 0 208 | if move in wins_rave: 209 | wins_rave[move][player] = 0 210 | else: 211 | wins_rave[move] = {player: 0} 212 | if t > self.max_depth: 213 | self.max_depth = t 214 | 215 | visited_states.add((player, move)) 216 | 217 | is_full = not len(availables) 218 | win, winner = self.has_a_winner(board) 219 | if is_full or win: 220 | break 221 | 222 | player = self.get_player(play_turn) 223 | 224 | # Back-propagation 225 | for player, move in visited_states: 226 | if (player, move) in plays: 227 | plays[(player, move)] += 1 # all visited moves 228 | if player == winner: 229 | wins[(player, move)] += 1 # only winner's moves 230 | if move in plays_rave: 231 | plays_rave[move] += 1 # no matter which player 232 | if winner in wins_rave[move]: 233 | wins_rave[move][winner] += 1 # each move and every player 234 | 235 | def get_player(self, players): 236 | p = players.pop(0) 237 | players.append(p) 238 | return p 239 | 240 | def select_one_move(self): 241 | # percent_wins, move = max( 242 | # (self.wins.get((self.player, move), 0) / 243 | # self.plays.get((self.player, move), 1), 244 | # move) 245 | # for move in self.board.availables) 246 | moves = {} 247 | for move in self.board.availables: 248 | # print('%f %d' % (100*self.wins.get((self.player, move), 0)/self.plays.get((self.player, move), 1), move)) 249 | moves[move] = 100*self.wins.get((self.player, move), 0)/self.plays.get((self.player, move), 1) 250 | comb = max(zip(moves.values(), moves.keys())) 251 | move = comb[1] 252 | print(move) 253 | 254 | 255 | # Display the statistics for each possible play, 256 | # first is MC value, second is AMAF value 257 | for x in sorted( 258 | ((100 * self.wins.get((self.player, move), 0) / 259 | self.plays.get((self.player, move), 1), 260 | 100 * self.wins_rave.get(move, {}).get(self.player, 0) / 261 | self.plays_rave.get(move, 1), 262 | self.wins.get((self.player, move), 0), 263 | self.plays.get((self.player, move), 0), 264 | self.wins_rave.get(move, {}).get(self.player, 0), 265 | self.plays_rave.get(move, 1), 266 | self.board.move_to_location(move)) 267 | for move in self.board.availables), 268 | reverse=True): 269 | print('{6}: {0:.2f}%--{1:.2f}% ({2} / {3})--({4} / {5})'.format(*x)) 270 | print(move) 271 | return move 272 | 273 | def adjacent_moves(self, board, player, plays): 274 | """ 275 | adjacent moves without statistics info 276 | """ 277 | moved = list(set(range(board.width * board.height)) - set(board.availables)) 278 | adjacents = set() 279 | width = board.width 280 | height = board.height 281 | 282 | for m in moved: 283 | h = m // width 284 | w = m % width 285 | if w < width - 1: 286 | adjacents.add(m + 1) # right 287 | if w > 0: 288 | adjacents.add(m - 1) # left 289 | if h < height - 1: 290 | adjacents.add(m + width) # upper 291 | if h > 0: 292 | adjacents.add(m - width) # lower 293 | if w < width - 1 and h < height - 1: 294 | adjacents.add(m + width + 1) # upper right 295 | if w > 0 and h < height - 1: 296 | adjacents.add(m + width - 1) # upper left 297 | if w < width - 1 and h > 0: 298 | adjacents.add(m - width + 1) # lower right 299 | if w > 0 and h > 0: 300 | adjacents.add(m - width - 1) # lower left 301 | 302 | adjacents = list(set(adjacents) - set(moved)) 303 | for move in adjacents: 304 | if plays.get((player, move)): 305 | adjacents.remove(move) 306 | return adjacents 307 | 308 | def has_a_winner(self, board): 309 | moved = list(set(range(board.width * board.height)) - set(board.availables)) 310 | if (len(moved) < self.n_in_row + 2): 311 | return False, -1 312 | 313 | width = board.width 314 | height = board.height 315 | states = board.states 316 | n = self.n_in_row 317 | for m in moved: 318 | h = m // width 319 | w = m % width 320 | player = states[m] 321 | 322 | if (w in range(width - n + 1) and 323 | len(set(states[i] for i in range(m, m + n))) == 1): 324 | return True, player 325 | 326 | if (h in range(height - n + 1) and 327 | len(set(states[i] for i in range(m, m + n * width, width))) == 1): 328 | return True, player 329 | 330 | if (w in range(width - n + 1) and h in range(height - n + 1) and 331 | len(set(states[i] for i in range(m, m + n * (width + 1), width + 1))) == 1): 332 | return True, player 333 | 334 | if (w in range(n - 1, width) and h in range(height - n + 1) and 335 | len(set(states[i] for i in range(m, m + n * (width - 1), width - 1))) == 1): 336 | return True, player 337 | 338 | return False, -1 339 | 340 | def __str__(self): 341 | return "AI" 342 | 343 | # class Game(object): 344 | # """ 345 | # game server 346 | # """ 347 | # 348 | # def __init__(self, board, **kwargs): 349 | # self.board = board 350 | # self.player = [1, 2] # player1 and player2 351 | # self.n_in_row = int(kwargs.get('n_in_row', 5)) 352 | # self.time = float(kwargs.get('time', 5)) 353 | # self.max_actions = int(kwargs.get('max_actions', 1000)) 354 | # 355 | # def start(self): 356 | # p1, p2 = self.init_player() 357 | # self.board.init_board() 358 | # 359 | # ai = MCTS(self.board, [p1, p2], self.n_in_row, self.time, self.max_actions) 360 | # human = Human(self.board, p2) 361 | # players = {} 362 | # players[p1] = ai 363 | # players[p2] = human 364 | # turn = [p1, p2] 365 | # shuffle(turn) 366 | # self.graphic(self.board, human, ai) 367 | # while (1): 368 | # p = turn.pop(0) 369 | # turn.append(p) 370 | # player_in_turn = players[p] 371 | # move = player_in_turn.get_action() 372 | # self.board.update(p, move) 373 | # self.graphic(self.board, human, ai) 374 | # end, winner = self.game_end(ai) 375 | # if end: 376 | # if winner != -1: 377 | # print("Game end. Winner is", players[winner]) 378 | # break 379 | # 380 | # def init_player(self): 381 | # plist = list(range(len(self.player))) 382 | # index1 = choice(plist) 383 | # plist.remove(index1) 384 | # index2 = choice(plist) 385 | # 386 | # return self.player[index1], self.player[index2] 387 | # 388 | # def game_end(self, ai): 389 | # win, winner = ai.has_a_winner(self.board) 390 | # if win: 391 | # return True, winner 392 | # elif not len(self.board.availables): 393 | # print("Game end. Tie") 394 | # return True, -1 395 | # return False, -1 396 | 397 | 398 | def finish(): 399 | pass 400 | -------------------------------------------------------------------------------- /report/acl2017.sty: -------------------------------------------------------------------------------- 1 | % 2017: modified to support DOI links in bibliography. Now uses 2 | % natbib package rather than defining citation commands in this file. 3 | % Use with acl_natbib.bst bib style. -- Dan Gildea 4 | 5 | % This is the LaTeX style for ACL 2016. It contains Margaret Mitchell's 6 | % line number adaptations (ported by Hai Zhao and Yannick Versley). 7 | 8 | % It is nearly identical to the style files for ACL 2015, 9 | % ACL 2014, EACL 2006, ACL2005, ACL 2002, ACL 2001, ACL 2000, 10 | % EACL 95 and EACL 99. 11 | % 12 | % Changes made include: adapt layout to A4 and centimeters, widen abstract 13 | 14 | % This is the LaTeX style file for ACL 2000. It is nearly identical to the 15 | % style files for EACL 95 and EACL 99. Minor changes include editing the 16 | % instructions to reflect use of \documentclass rather than \documentstyle 17 | % and removing the white space before the title on the first page 18 | % -- John Chen, June 29, 2000 19 | 20 | % This is the LaTeX style file for EACL-95. It is identical to the 21 | % style file for ANLP '94 except that the margins are adjusted for A4 22 | % paper. -- abney 13 Dec 94 23 | 24 | % The ANLP '94 style file is a slightly modified 25 | % version of the style used for AAAI and IJCAI, using some changes 26 | % prepared by Fernando Pereira and others and some minor changes 27 | % by Paul Jacobs. 28 | 29 | % Papers prepared using the aclsub.sty file and acl.bst bibtex style 30 | % should be easily converted to final format using this style. 31 | % (1) Submission information (\wordcount, \subject, and \makeidpage) 32 | % should be removed. 33 | % (2) \summary should be removed. The summary material should come 34 | % after \maketitle and should be in the ``abstract'' environment 35 | % (between \begin{abstract} and \end{abstract}). 36 | % (3) Check all citations. This style should handle citations correctly 37 | % and also allows multiple citations separated by semicolons. 38 | % (4) Check figures and examples. Because the final format is double- 39 | % column, some adjustments may have to be made to fit text in the column 40 | % or to choose full-width (\figure*} figures. 41 | 42 | % Place this in a file called aclap.sty in the TeX search path. 43 | % (Placing it in the same directory as the paper should also work.) 44 | 45 | % Prepared by Peter F. Patel-Schneider, liberally using the ideas of 46 | % other style hackers, including Barbara Beeton. 47 | % This style is NOT guaranteed to work. It is provided in the hope 48 | % that it will make the preparation of papers easier. 49 | % 50 | % There are undoubtably bugs in this style. If you make bug fixes, 51 | % improvements, etc. please let me know. My e-mail address is: 52 | % pfps@research.att.com 53 | 54 | % Papers are to be prepared using the ``acl_natbib'' bibliography style, 55 | % as follows: 56 | % \documentclass[11pt]{article} 57 | % \usepackage{acl2000} 58 | % \title{Title} 59 | % \author{Author 1 \and Author 2 \\ Address line \\ Address line \And 60 | % Author 3 \\ Address line \\ Address line} 61 | % \begin{document} 62 | % ... 63 | % \bibliography{bibliography-file} 64 | % \bibliographystyle{acl_natbib} 65 | % \end{document} 66 | 67 | % Author information can be set in various styles: 68 | % For several authors from the same institution: 69 | % \author{Author 1 \and ... \and Author n \\ 70 | % Address line \\ ... \\ Address line} 71 | % if the names do not fit well on one line use 72 | % Author 1 \\ {\bf Author 2} \\ ... \\ {\bf Author n} \\ 73 | % For authors from different institutions: 74 | % \author{Author 1 \\ Address line \\ ... \\ Address line 75 | % \And ... \And 76 | % Author n \\ Address line \\ ... \\ Address line} 77 | % To start a seperate ``row'' of authors use \AND, as in 78 | % \author{Author 1 \\ Address line \\ ... \\ Address line 79 | % \AND 80 | % Author 2 \\ Address line \\ ... \\ Address line \And 81 | % Author 3 \\ Address line \\ ... \\ Address line} 82 | 83 | % If the title and author information does not fit in the area allocated, 84 | % place \setlength\titlebox{} right after 85 | % \usepackage{acl2015} 86 | % where can be something larger than 5cm 87 | 88 | % include hyperref, unless user specifies nohyperref option like this: 89 | % \usepackage[nohyperref]{acl2017} 90 | \newif\ifacl@hyperref 91 | \DeclareOption{hyperref}{\acl@hyperreftrue} 92 | \DeclareOption{nohyperref}{\acl@hyperreffalse} 93 | \ExecuteOptions{hyperref} % default is to use hyperref 94 | \ProcessOptions\relax 95 | \ifacl@hyperref 96 | \RequirePackage{hyperref} 97 | \usepackage{xcolor} % make links dark blue 98 | \definecolor{darkblue}{rgb}{0, 0, 0.5} 99 | \hypersetup{colorlinks=true,citecolor=darkblue, linkcolor=darkblue, urlcolor=darkblue} 100 | \else 101 | % This definition is used if the hyperref package is not loaded. 102 | % It provides a backup, no-op definiton of \href. 103 | % This is necessary because \href command is used in the acl_natbib.bst file. 104 | \def\href#1#2{{#2}} 105 | \fi 106 | 107 | \typeout{Conference Style for ACL 2017} 108 | 109 | % NOTE: Some laser printers have a serious problem printing TeX output. 110 | % These printing devices, commonly known as ``write-white'' laser 111 | % printers, tend to make characters too light. To get around this 112 | % problem, a darker set of fonts must be created for these devices. 113 | % 114 | 115 | \newcommand{\Thanks}[1]{\thanks{\ #1}} 116 | 117 | % A4 modified by Eneko; again modified by Alexander for 5cm titlebox 118 | \setlength{\paperwidth}{21cm} % A4 119 | \setlength{\paperheight}{29.7cm}% A4 120 | \setlength\topmargin{-0.5cm} 121 | \setlength\oddsidemargin{0cm} 122 | \setlength\textheight{24.7cm} 123 | \setlength\textwidth{16.0cm} 124 | \setlength\columnsep{0.6cm} 125 | \newlength\titlebox 126 | \setlength\titlebox{5cm} 127 | \setlength\headheight{5pt} 128 | \setlength\headsep{0pt} 129 | \thispagestyle{empty} 130 | \pagestyle{empty} 131 | 132 | 133 | \flushbottom \twocolumn \sloppy 134 | 135 | % We're never going to need a table of contents, so just flush it to 136 | % save space --- suggested by drstrip@sandia-2 137 | \def\addcontentsline#1#2#3{} 138 | 139 | \newif\ifaclfinal 140 | \aclfinalfalse 141 | \def\aclfinalcopy{\global\aclfinaltrue} 142 | 143 | %% ----- Set up hooks to repeat content on every page of the output doc, 144 | %% necessary for the line numbers in the submitted version. --MM 145 | %% 146 | %% Copied from CVPR 2015's cvpr_eso.sty, which appears to be largely copied from everyshi.sty. 147 | %% 148 | %% Original cvpr_eso.sty available at: http://www.pamitc.org/cvpr15/author_guidelines.php 149 | %% Original evershi.sty available at: https://www.ctan.org/pkg/everyshi 150 | %% 151 | %% Copyright (C) 2001 Martin Schr\"oder: 152 | %% 153 | %% Martin Schr"oder 154 | %% Cr"usemannallee 3 155 | %% D-28213 Bremen 156 | %% Martin.Schroeder@ACM.org 157 | %% 158 | %% This program may be redistributed and/or modified under the terms 159 | %% of the LaTeX Project Public License, either version 1.0 of this 160 | %% license, or (at your option) any later version. 161 | %% The latest version of this license is in 162 | %% CTAN:macros/latex/base/lppl.txt. 163 | %% 164 | %% Happy users are requested to send [Martin] a postcard. :-) 165 | %% 166 | \newcommand{\@EveryShipoutACL@Hook}{} 167 | \newcommand{\@EveryShipoutACL@AtNextHook}{} 168 | \newcommand*{\EveryShipoutACL}[1] 169 | {\g@addto@macro\@EveryShipoutACL@Hook{#1}} 170 | \newcommand*{\AtNextShipoutACL@}[1] 171 | {\g@addto@macro\@EveryShipoutACL@AtNextHook{#1}} 172 | \newcommand{\@EveryShipoutACL@Shipout}{% 173 | \afterassignment\@EveryShipoutACL@Test 174 | \global\setbox\@cclv= % 175 | } 176 | \newcommand{\@EveryShipoutACL@Test}{% 177 | \ifvoid\@cclv\relax 178 | \aftergroup\@EveryShipoutACL@Output 179 | \else 180 | \@EveryShipoutACL@Output 181 | \fi% 182 | } 183 | \newcommand{\@EveryShipoutACL@Output}{% 184 | \@EveryShipoutACL@Hook% 185 | \@EveryShipoutACL@AtNextHook% 186 | \gdef\@EveryShipoutACL@AtNextHook{}% 187 | \@EveryShipoutACL@Org@Shipout\box\@cclv% 188 | } 189 | \newcommand{\@EveryShipoutACL@Org@Shipout}{} 190 | \newcommand*{\@EveryShipoutACL@Init}{% 191 | \message{ABD: EveryShipout initializing macros}% 192 | \let\@EveryShipoutACL@Org@Shipout\shipout 193 | \let\shipout\@EveryShipoutACL@Shipout 194 | } 195 | \AtBeginDocument{\@EveryShipoutACL@Init} 196 | 197 | %% ----- Set up for placing additional items into the submitted version --MM 198 | %% 199 | %% Based on eso-pic.sty 200 | %% 201 | %% Original available at: https://www.ctan.org/tex-archive/macros/latex/contrib/eso-pic 202 | %% Copyright (C) 1998-2002 by Rolf Niepraschk 203 | %% 204 | %% Which may be distributed and/or modified under the conditions of 205 | %% the LaTeX Project Public License, either version 1.2 of this license 206 | %% or (at your option) any later version. The latest version of this 207 | %% license is in: 208 | %% 209 | %% http://www.latex-project.org/lppl.txt 210 | %% 211 | %% and version 1.2 or later is part of all distributions of LaTeX version 212 | %% 1999/12/01 or later. 213 | %% 214 | %% In contrast to the original, we do not include the definitions for/using: 215 | %% gridpicture, div[2], isMEMOIR[1], gridSetup[6][], subgridstyle{dotted}, labelfactor{}, gap{}, gridunitname{}, gridunit{}, gridlines{\thinlines}, subgridlines{\thinlines}, the {keyval} package, evenside margin, nor any definitions with 'color'. 216 | %% 217 | %% These are beyond what is needed for the NAACL style. 218 | %% 219 | \newcommand\LenToUnit[1]{#1\@gobble} 220 | \newcommand\AtPageUpperLeft[1]{% 221 | \begingroup 222 | \@tempdima=0pt\relax\@tempdimb=\ESO@yoffsetI\relax 223 | \put(\LenToUnit{\@tempdima},\LenToUnit{\@tempdimb}){#1}% 224 | \endgroup 225 | } 226 | \newcommand\AtPageLowerLeft[1]{\AtPageUpperLeft{% 227 | \put(0,\LenToUnit{-\paperheight}){#1}}} 228 | \newcommand\AtPageCenter[1]{\AtPageUpperLeft{% 229 | \put(\LenToUnit{.5\paperwidth},\LenToUnit{-.5\paperheight}){#1}}} 230 | \newcommand\AtPageLowerCenter[1]{\AtPageUpperLeft{% 231 | \put(\LenToUnit{.5\paperwidth},\LenToUnit{-\paperheight}){#1}}}% 232 | \newcommand\AtPageLowishCenter[1]{\AtPageUpperLeft{% 233 | \put(\LenToUnit{.5\paperwidth},\LenToUnit{-.96\paperheight}){#1}}} 234 | \newcommand\AtTextUpperLeft[1]{% 235 | \begingroup 236 | \setlength\@tempdima{1in}% 237 | \advance\@tempdima\oddsidemargin% 238 | \@tempdimb=\ESO@yoffsetI\relax\advance\@tempdimb-1in\relax% 239 | \advance\@tempdimb-\topmargin% 240 | \advance\@tempdimb-\headheight\advance\@tempdimb-\headsep% 241 | \put(\LenToUnit{\@tempdima},\LenToUnit{\@tempdimb}){#1}% 242 | \endgroup 243 | } 244 | \newcommand\AtTextLowerLeft[1]{\AtTextUpperLeft{% 245 | \put(0,\LenToUnit{-\textheight}){#1}}} 246 | \newcommand\AtTextCenter[1]{\AtTextUpperLeft{% 247 | \put(\LenToUnit{.5\textwidth},\LenToUnit{-.5\textheight}){#1}}} 248 | \newcommand{\ESO@HookI}{} \newcommand{\ESO@HookII}{} 249 | \newcommand{\ESO@HookIII}{} 250 | \newcommand{\AddToShipoutPicture}{% 251 | \@ifstar{\g@addto@macro\ESO@HookII}{\g@addto@macro\ESO@HookI}} 252 | \newcommand{\ClearShipoutPicture}{\global\let\ESO@HookI\@empty} 253 | \newcommand{\@ShipoutPicture}{% 254 | \bgroup 255 | \@tempswafalse% 256 | \ifx\ESO@HookI\@empty\else\@tempswatrue\fi% 257 | \ifx\ESO@HookII\@empty\else\@tempswatrue\fi% 258 | \ifx\ESO@HookIII\@empty\else\@tempswatrue\fi% 259 | \if@tempswa% 260 | \@tempdima=1in\@tempdimb=-\@tempdima% 261 | \advance\@tempdimb\ESO@yoffsetI% 262 | \unitlength=1pt% 263 | \global\setbox\@cclv\vbox{% 264 | \vbox{\let\protect\relax 265 | \pictur@(0,0)(\strip@pt\@tempdima,\strip@pt\@tempdimb)% 266 | \ESO@HookIII\ESO@HookI\ESO@HookII% 267 | \global\let\ESO@HookII\@empty% 268 | \endpicture}% 269 | \nointerlineskip% 270 | \box\@cclv}% 271 | \fi 272 | \egroup 273 | } 274 | \EveryShipoutACL{\@ShipoutPicture} 275 | \newif\ifESO@dvips\ESO@dvipsfalse 276 | \newif\ifESO@grid\ESO@gridfalse 277 | \newif\ifESO@texcoord\ESO@texcoordfalse 278 | \newcommand*\ESO@griddelta{}\newcommand*\ESO@griddeltaY{} 279 | \newcommand*\ESO@gridDelta{}\newcommand*\ESO@gridDeltaY{} 280 | \newcommand*\ESO@yoffsetI{}\newcommand*\ESO@yoffsetII{} 281 | \ifESO@texcoord 282 | \def\ESO@yoffsetI{0pt}\def\ESO@yoffsetII{-\paperheight} 283 | \edef\ESO@griddeltaY{-\ESO@griddelta}\edef\ESO@gridDeltaY{-\ESO@gridDelta} 284 | \else 285 | \def\ESO@yoffsetI{\paperheight}\def\ESO@yoffsetII{0pt} 286 | \edef\ESO@griddeltaY{\ESO@griddelta}\edef\ESO@gridDeltaY{\ESO@gridDelta} 287 | \fi 288 | 289 | 290 | %% ----- Submitted version markup: Page numbers, ruler, and confidentiality. Using ideas/code from cvpr.sty 2015. --MM 291 | 292 | \font\naaclhv = phvb at 8pt 293 | 294 | %% Define vruler %% 295 | 296 | %\makeatletter 297 | \newbox\aclrulerbox 298 | \newcount\aclrulercount 299 | \newdimen\aclruleroffset 300 | \newdimen\cv@lineheight 301 | \newdimen\cv@boxheight 302 | \newbox\cv@tmpbox 303 | \newcount\cv@refno 304 | \newcount\cv@tot 305 | % NUMBER with left flushed zeros \fillzeros[] 306 | \newcount\cv@tmpc@ \newcount\cv@tmpc 307 | \def\fillzeros[#1]#2{\cv@tmpc@=#2\relax\ifnum\cv@tmpc@<0\cv@tmpc@=-\cv@tmpc@\fi 308 | \cv@tmpc=1 % 309 | \loop\ifnum\cv@tmpc@<10 \else \divide\cv@tmpc@ by 10 \advance\cv@tmpc by 1 \fi 310 | \ifnum\cv@tmpc@=10\relax\cv@tmpc@=11\relax\fi \ifnum\cv@tmpc@>10 \repeat 311 | \ifnum#2<0\advance\cv@tmpc1\relax-\fi 312 | \loop\ifnum\cv@tmpc<#1\relax0\advance\cv@tmpc1\relax\fi \ifnum\cv@tmpc<#1 \repeat 313 | \cv@tmpc@=#2\relax\ifnum\cv@tmpc@<0\cv@tmpc@=-\cv@tmpc@\fi \relax\the\cv@tmpc@}% 314 | % \makevruler[][][][][] 315 | \def\makevruler[#1][#2][#3][#4][#5]{\begingroup\offinterlineskip 316 | \textheight=#5\vbadness=10000\vfuzz=120ex\overfullrule=0pt% 317 | \global\setbox\aclrulerbox=\vbox to \textheight{% 318 | {\parskip=0pt\hfuzz=150em\cv@boxheight=\textheight 319 | \cv@lineheight=#1\global\aclrulercount=#2% 320 | \cv@tot\cv@boxheight\divide\cv@tot\cv@lineheight\advance\cv@tot2% 321 | \cv@refno1\vskip-\cv@lineheight\vskip1ex% 322 | \loop\setbox\cv@tmpbox=\hbox to0cm{{\naaclhv\hfil\fillzeros[#4]\aclrulercount}}% 323 | \ht\cv@tmpbox\cv@lineheight\dp\cv@tmpbox0pt\box\cv@tmpbox\break 324 | \advance\cv@refno1\global\advance\aclrulercount#3\relax 325 | \ifnum\cv@refno<\cv@tot\repeat}}\endgroup}% 326 | %\makeatother 327 | 328 | 329 | \def\aclpaperid{***} 330 | \def\confidential{ACL 2017 Submission~\aclpaperid. Confidential Review Copy. DO NOT DISTRIBUTE.} 331 | 332 | %% Page numbering, Vruler and Confidentiality %% 333 | % \makevruler[][][][][] 334 | \def\aclruler#1{\makevruler[14.17pt][#1][1][3][\textheight]\usebox{\aclrulerbox}} 335 | \def\leftoffset{-2.1cm} %original: -45pt 336 | \def\rightoffset{17.5cm} %original: 500pt 337 | \ifaclfinal\else\pagenumbering{arabic} 338 | \AddToShipoutPicture{% 339 | \ifaclfinal\else 340 | \AtPageLowishCenter{\thepage} 341 | \aclruleroffset=\textheight 342 | \advance\aclruleroffset4pt 343 | \AtTextUpperLeft{% 344 | \put(\LenToUnit{\leftoffset},\LenToUnit{-\aclruleroffset}){%left ruler 345 | \aclruler{\aclrulercount}} 346 | \put(\LenToUnit{\rightoffset},\LenToUnit{-\aclruleroffset}){%right ruler 347 | \aclruler{\aclrulercount}} 348 | } 349 | \AtTextUpperLeft{%confidential 350 | \put(0,\LenToUnit{1cm}){\parbox{\textwidth}{\centering\naaclhv\confidential}} 351 | } 352 | \fi 353 | } 354 | 355 | %%%% ----- End settings for placing additional items into the submitted version --MM ----- %%%% 356 | 357 | %%%% ----- Begin settings for both submitted and camera-ready version ----- %%%% 358 | 359 | %% Title and Authors %% 360 | 361 | \newcommand\outauthor{ 362 | \begin{tabular}[t]{c} 363 | \ifaclfinal 364 | \bf\@author 365 | \else 366 | % Avoiding common accidental de-anonymization issue. --MM 367 | \bf Anonymous ACL submission 368 | \fi 369 | \end{tabular}} 370 | 371 | % Changing the expanded titlebox for submissions to 2.5 in (rather than 6.5cm) 372 | % and moving it to the style sheet, rather than within the example tex file. --MM 373 | \ifaclfinal 374 | \else 375 | \addtolength\titlebox{.25in} 376 | \fi 377 | % Mostly taken from deproc. 378 | \def\maketitle{\par 379 | \begingroup 380 | \def\thefootnote{\fnsymbol{footnote}} 381 | \def\@makefnmark{\hbox to 0pt{$^{\@thefnmark}$\hss}} 382 | \twocolumn[\@maketitle] \@thanks 383 | \endgroup 384 | \setcounter{footnote}{0} 385 | \let\maketitle\relax \let\@maketitle\relax 386 | \gdef\@thanks{}\gdef\@author{}\gdef\@title{}\let\thanks\relax} 387 | \def\@maketitle{\vbox to \titlebox{\hsize\textwidth 388 | \linewidth\hsize \vskip 0.125in minus 0.125in \centering 389 | {\Large\bf \@title \par} \vskip 0.2in plus 1fil minus 0.1in 390 | {\def\and{\unskip\enspace{\rm and}\enspace}% 391 | \def\And{\end{tabular}\hss \egroup \hskip 1in plus 2fil 392 | \hbox to 0pt\bgroup\hss \begin{tabular}[t]{c}\bf}% 393 | \def\AND{\end{tabular}\hss\egroup \hfil\hfil\egroup 394 | \vskip 0.25in plus 1fil minus 0.125in 395 | \hbox to \linewidth\bgroup\large \hfil\hfil 396 | \hbox to 0pt\bgroup\hss \begin{tabular}[t]{c}\bf} 397 | \hbox to \linewidth\bgroup\large \hfil\hfil 398 | \hbox to 0pt\bgroup\hss 399 | \outauthor 400 | \hss\egroup 401 | \hfil\hfil\egroup} 402 | \vskip 0.3in plus 2fil minus 0.1in 403 | }} 404 | 405 | % margins for abstract 406 | \renewenvironment{abstract}% 407 | {\centerline{\large\bf Abstract}% 408 | \begin{list}{}% 409 | {\setlength{\rightmargin}{0.6cm}% 410 | \setlength{\leftmargin}{0.6cm}}% 411 | \item[]\ignorespaces}% 412 | {\unskip\end{list}} 413 | 414 | %\renewenvironment{abstract}{\centerline{\large\bf 415 | % Abstract}\vspace{0.5ex}\begin{quote}}{\par\end{quote}\vskip 1ex} 416 | 417 | \RequirePackage{natbib} 418 | % for citation commands in the .tex, authors can use: 419 | % \citep, \citet, and \citeyearpar for compatibility with natbib, or 420 | % \cite, \newcite, and \shortcite for compatibility with older ACL .sty files 421 | \renewcommand\cite{\citep} % to get "(Author Year)" with natbib 422 | \newcommand\shortcite{\citeyearpar}% to get "(Year)" with natbib 423 | \newcommand\newcite{\citet} % to get "Author (Year)" with natbib 424 | 425 | 426 | % bibliography 427 | 428 | \def\@up#1{\raise.2ex\hbox{#1}} 429 | 430 | % Don't put a label in the bibliography at all. Just use the unlabeled format 431 | % instead. 432 | \def\thebibliography#1{\vskip\parskip% 433 | \vskip\baselineskip% 434 | \def\baselinestretch{1}% 435 | \ifx\@currsize\normalsize\@normalsize\else\@currsize\fi% 436 | \vskip-\parskip% 437 | \vskip-\baselineskip% 438 | \section*{References\@mkboth 439 | {References}{References}}\list 440 | {}{\setlength{\labelwidth}{0pt}\setlength{\leftmargin}{\parindent} 441 | \setlength{\itemindent}{-\parindent}} 442 | \def\newblock{\hskip .11em plus .33em minus -.07em} 443 | \sloppy\clubpenalty4000\widowpenalty4000 444 | \sfcode`\.=1000\relax} 445 | \let\endthebibliography=\endlist 446 | 447 | 448 | % Allow for a bibliography of sources of attested examples 449 | \def\thesourcebibliography#1{\vskip\parskip% 450 | \vskip\baselineskip% 451 | \def\baselinestretch{1}% 452 | \ifx\@currsize\normalsize\@normalsize\else\@currsize\fi% 453 | \vskip-\parskip% 454 | \vskip-\baselineskip% 455 | \section*{Sources of Attested Examples\@mkboth 456 | {Sources of Attested Examples}{Sources of Attested Examples}}\list 457 | {}{\setlength{\labelwidth}{0pt}\setlength{\leftmargin}{\parindent} 458 | \setlength{\itemindent}{-\parindent}} 459 | \def\newblock{\hskip .11em plus .33em minus -.07em} 460 | \sloppy\clubpenalty4000\widowpenalty4000 461 | \sfcode`\.=1000\relax} 462 | \let\endthesourcebibliography=\endlist 463 | 464 | % sections with less space 465 | \def\section{\@startsection {section}{1}{\z@}{-2.0ex plus 466 | -0.5ex minus -.2ex}{1.5ex plus 0.3ex minus .2ex}{\large\bf\raggedright}} 467 | \def\subsection{\@startsection{subsection}{2}{\z@}{-1.8ex plus 468 | -0.5ex minus -.2ex}{0.8ex plus .2ex}{\normalsize\bf\raggedright}} 469 | %% changed by KO to - values to get teh initial parindent right 470 | \def\subsubsection{\@startsection{subsubsection}{3}{\z@}{-1.5ex plus 471 | -0.5ex minus -.2ex}{0.5ex plus .2ex}{\normalsize\bf\raggedright}} 472 | \def\paragraph{\@startsection{paragraph}{4}{\z@}{1.5ex plus 473 | 0.5ex minus .2ex}{-1em}{\normalsize\bf}} 474 | \def\subparagraph{\@startsection{subparagraph}{5}{\parindent}{1.5ex plus 475 | 0.5ex minus .2ex}{-1em}{\normalsize\bf}} 476 | 477 | % Footnotes 478 | \footnotesep 6.65pt % 479 | \skip\footins 9pt plus 4pt minus 2pt 480 | \def\footnoterule{\kern-3pt \hrule width 5pc \kern 2.6pt } 481 | \setcounter{footnote}{0} 482 | 483 | % Lists and paragraphs 484 | \parindent 1em 485 | \topsep 4pt plus 1pt minus 2pt 486 | \partopsep 1pt plus 0.5pt minus 0.5pt 487 | \itemsep 2pt plus 1pt minus 0.5pt 488 | \parsep 2pt plus 1pt minus 0.5pt 489 | 490 | \leftmargin 2em \leftmargini\leftmargin \leftmarginii 2em 491 | \leftmarginiii 1.5em \leftmarginiv 1.0em \leftmarginv .5em \leftmarginvi .5em 492 | \labelwidth\leftmargini\advance\labelwidth-\labelsep \labelsep 5pt 493 | 494 | \def\@listi{\leftmargin\leftmargini} 495 | \def\@listii{\leftmargin\leftmarginii 496 | \labelwidth\leftmarginii\advance\labelwidth-\labelsep 497 | \topsep 2pt plus 1pt minus 0.5pt 498 | \parsep 1pt plus 0.5pt minus 0.5pt 499 | \itemsep \parsep} 500 | \def\@listiii{\leftmargin\leftmarginiii 501 | \labelwidth\leftmarginiii\advance\labelwidth-\labelsep 502 | \topsep 1pt plus 0.5pt minus 0.5pt 503 | \parsep \z@ \partopsep 0.5pt plus 0pt minus 0.5pt 504 | \itemsep \topsep} 505 | \def\@listiv{\leftmargin\leftmarginiv 506 | \labelwidth\leftmarginiv\advance\labelwidth-\labelsep} 507 | \def\@listv{\leftmargin\leftmarginv 508 | \labelwidth\leftmarginv\advance\labelwidth-\labelsep} 509 | \def\@listvi{\leftmargin\leftmarginvi 510 | \labelwidth\leftmarginvi\advance\labelwidth-\labelsep} 511 | 512 | \abovedisplayskip 7pt plus2pt minus5pt% 513 | \belowdisplayskip \abovedisplayskip 514 | \abovedisplayshortskip 0pt plus3pt% 515 | \belowdisplayshortskip 4pt plus3pt minus3pt% 516 | 517 | % Less leading in most fonts (due to the narrow columns) 518 | % The choices were between 1-pt and 1.5-pt leading 519 | \def\@normalsize{\@setsize\normalsize{11pt}\xpt\@xpt} 520 | \def\small{\@setsize\small{10pt}\ixpt\@ixpt} 521 | \def\footnotesize{\@setsize\footnotesize{10pt}\ixpt\@ixpt} 522 | \def\scriptsize{\@setsize\scriptsize{8pt}\viipt\@viipt} 523 | \def\tiny{\@setsize\tiny{7pt}\vipt\@vipt} 524 | \def\large{\@setsize\large{14pt}\xiipt\@xiipt} 525 | \def\Large{\@setsize\Large{16pt}\xivpt\@xivpt} 526 | \def\LARGE{\@setsize\LARGE{20pt}\xviipt\@xviipt} 527 | \def\huge{\@setsize\huge{23pt}\xxpt\@xxpt} 528 | \def\Huge{\@setsize\Huge{28pt}\xxvpt\@xxvpt} 529 | -------------------------------------------------------------------------------- /report/acl17-latex/acl2017.sty: -------------------------------------------------------------------------------- 1 | % 2017: modified to support DOI links in bibliography. Now uses 2 | % natbib package rather than defining citation commands in this file. 3 | % Use with acl_natbib.bst bib style. -- Dan Gildea 4 | 5 | % This is the LaTeX style for ACL 2016. It contains Margaret Mitchell's 6 | % line number adaptations (ported by Hai Zhao and Yannick Versley). 7 | 8 | % It is nearly identical to the style files for ACL 2015, 9 | % ACL 2014, EACL 2006, ACL2005, ACL 2002, ACL 2001, ACL 2000, 10 | % EACL 95 and EACL 99. 11 | % 12 | % Changes made include: adapt layout to A4 and centimeters, widen abstract 13 | 14 | % This is the LaTeX style file for ACL 2000. It is nearly identical to the 15 | % style files for EACL 95 and EACL 99. Minor changes include editing the 16 | % instructions to reflect use of \documentclass rather than \documentstyle 17 | % and removing the white space before the title on the first page 18 | % -- John Chen, June 29, 2000 19 | 20 | % This is the LaTeX style file for EACL-95. It is identical to the 21 | % style file for ANLP '94 except that the margins are adjusted for A4 22 | % paper. -- abney 13 Dec 94 23 | 24 | % The ANLP '94 style file is a slightly modified 25 | % version of the style used for AAAI and IJCAI, using some changes 26 | % prepared by Fernando Pereira and others and some minor changes 27 | % by Paul Jacobs. 28 | 29 | % Papers prepared using the aclsub.sty file and acl.bst bibtex style 30 | % should be easily converted to final format using this style. 31 | % (1) Submission information (\wordcount, \subject, and \makeidpage) 32 | % should be removed. 33 | % (2) \summary should be removed. The summary material should come 34 | % after \maketitle and should be in the ``abstract'' environment 35 | % (between \begin{abstract} and \end{abstract}). 36 | % (3) Check all citations. This style should handle citations correctly 37 | % and also allows multiple citations separated by semicolons. 38 | % (4) Check figures and examples. Because the final format is double- 39 | % column, some adjustments may have to be made to fit text in the column 40 | % or to choose full-width (\figure*} figures. 41 | 42 | % Place this in a file called aclap.sty in the TeX search path. 43 | % (Placing it in the same directory as the paper should also work.) 44 | 45 | % Prepared by Peter F. Patel-Schneider, liberally using the ideas of 46 | % other style hackers, including Barbara Beeton. 47 | % This style is NOT guaranteed to work. It is provided in the hope 48 | % that it will make the preparation of papers easier. 49 | % 50 | % There are undoubtably bugs in this style. If you make bug fixes, 51 | % improvements, etc. please let me know. My e-mail address is: 52 | % pfps@research.att.com 53 | 54 | % Papers are to be prepared using the ``acl_natbib'' bibliography style, 55 | % as follows: 56 | % \documentclass[11pt]{article} 57 | % \usepackage{acl2000} 58 | % \title{Title} 59 | % \author{Author 1 \and Author 2 \\ Address line \\ Address line \And 60 | % Author 3 \\ Address line \\ Address line} 61 | % \begin{document} 62 | % ... 63 | % \bibliography{bibliography-file} 64 | % \bibliographystyle{acl_natbib} 65 | % \end{document} 66 | 67 | % Author information can be set in various styles: 68 | % For several authors from the same institution: 69 | % \author{Author 1 \and ... \and Author n \\ 70 | % Address line \\ ... \\ Address line} 71 | % if the names do not fit well on one line use 72 | % Author 1 \\ {\bf Author 2} \\ ... \\ {\bf Author n} \\ 73 | % For authors from different institutions: 74 | % \author{Author 1 \\ Address line \\ ... \\ Address line 75 | % \And ... \And 76 | % Author n \\ Address line \\ ... \\ Address line} 77 | % To start a seperate ``row'' of authors use \AND, as in 78 | % \author{Author 1 \\ Address line \\ ... \\ Address line 79 | % \AND 80 | % Author 2 \\ Address line \\ ... \\ Address line \And 81 | % Author 3 \\ Address line \\ ... \\ Address line} 82 | 83 | % If the title and author information does not fit in the area allocated, 84 | % place \setlength\titlebox{} right after 85 | % \usepackage{acl2015} 86 | % where can be something larger than 5cm 87 | 88 | % include hyperref, unless user specifies nohyperref option like this: 89 | % \usepackage[nohyperref]{acl2017} 90 | \newif\ifacl@hyperref 91 | \DeclareOption{hyperref}{\acl@hyperreftrue} 92 | \DeclareOption{nohyperref}{\acl@hyperreffalse} 93 | \ExecuteOptions{hyperref} % default is to use hyperref 94 | \ProcessOptions\relax 95 | \ifacl@hyperref 96 | \RequirePackage{hyperref} 97 | \usepackage{xcolor} % make links dark blue 98 | \definecolor{darkblue}{rgb}{0, 0, 0.5} 99 | \hypersetup{colorlinks=true,citecolor=darkblue, linkcolor=darkblue, urlcolor=darkblue} 100 | \else 101 | % This definition is used if the hyperref package is not loaded. 102 | % It provides a backup, no-op definiton of \href. 103 | % This is necessary because \href command is used in the acl_natbib.bst file. 104 | \def\href#1#2{{#2}} 105 | \fi 106 | 107 | \typeout{Conference Style for ACL 2017} 108 | 109 | % NOTE: Some laser printers have a serious problem printing TeX output. 110 | % These printing devices, commonly known as ``write-white'' laser 111 | % printers, tend to make characters too light. To get around this 112 | % problem, a darker set of fonts must be created for these devices. 113 | % 114 | 115 | \newcommand{\Thanks}[1]{\thanks{\ #1}} 116 | 117 | % A4 modified by Eneko; again modified by Alexander for 5cm titlebox 118 | \setlength{\paperwidth}{21cm} % A4 119 | \setlength{\paperheight}{29.7cm}% A4 120 | \setlength\topmargin{-0.5cm} 121 | \setlength\oddsidemargin{0cm} 122 | \setlength\textheight{24.7cm} 123 | \setlength\textwidth{16.0cm} 124 | \setlength\columnsep{0.6cm} 125 | \newlength\titlebox 126 | \setlength\titlebox{5cm} 127 | \setlength\headheight{5pt} 128 | \setlength\headsep{0pt} 129 | \thispagestyle{empty} 130 | \pagestyle{empty} 131 | 132 | 133 | \flushbottom \twocolumn \sloppy 134 | 135 | % We're never going to need a table of contents, so just flush it to 136 | % save space --- suggested by drstrip@sandia-2 137 | \def\addcontentsline#1#2#3{} 138 | 139 | \newif\ifaclfinal 140 | \aclfinalfalse 141 | \def\aclfinalcopy{\global\aclfinaltrue} 142 | 143 | %% ----- Set up hooks to repeat content on every page of the output doc, 144 | %% necessary for the line numbers in the submitted version. --MM 145 | %% 146 | %% Copied from CVPR 2015's cvpr_eso.sty, which appears to be largely copied from everyshi.sty. 147 | %% 148 | %% Original cvpr_eso.sty available at: http://www.pamitc.org/cvpr15/author_guidelines.php 149 | %% Original evershi.sty available at: https://www.ctan.org/pkg/everyshi 150 | %% 151 | %% Copyright (C) 2001 Martin Schr\"oder: 152 | %% 153 | %% Martin Schr"oder 154 | %% Cr"usemannallee 3 155 | %% D-28213 Bremen 156 | %% Martin.Schroeder@ACM.org 157 | %% 158 | %% This program may be redistributed and/or modified under the terms 159 | %% of the LaTeX Project Public License, either version 1.0 of this 160 | %% license, or (at your option) any later version. 161 | %% The latest version of this license is in 162 | %% CTAN:macros/latex/base/lppl.txt. 163 | %% 164 | %% Happy users are requested to send [Martin] a postcard. :-) 165 | %% 166 | \newcommand{\@EveryShipoutACL@Hook}{} 167 | \newcommand{\@EveryShipoutACL@AtNextHook}{} 168 | \newcommand*{\EveryShipoutACL}[1] 169 | {\g@addto@macro\@EveryShipoutACL@Hook{#1}} 170 | \newcommand*{\AtNextShipoutACL@}[1] 171 | {\g@addto@macro\@EveryShipoutACL@AtNextHook{#1}} 172 | \newcommand{\@EveryShipoutACL@Shipout}{% 173 | \afterassignment\@EveryShipoutACL@Test 174 | \global\setbox\@cclv= % 175 | } 176 | \newcommand{\@EveryShipoutACL@Test}{% 177 | \ifvoid\@cclv\relax 178 | \aftergroup\@EveryShipoutACL@Output 179 | \else 180 | \@EveryShipoutACL@Output 181 | \fi% 182 | } 183 | \newcommand{\@EveryShipoutACL@Output}{% 184 | \@EveryShipoutACL@Hook% 185 | \@EveryShipoutACL@AtNextHook% 186 | \gdef\@EveryShipoutACL@AtNextHook{}% 187 | \@EveryShipoutACL@Org@Shipout\box\@cclv% 188 | } 189 | \newcommand{\@EveryShipoutACL@Org@Shipout}{} 190 | \newcommand*{\@EveryShipoutACL@Init}{% 191 | \message{ABD: EveryShipout initializing macros}% 192 | \let\@EveryShipoutACL@Org@Shipout\shipout 193 | \let\shipout\@EveryShipoutACL@Shipout 194 | } 195 | \AtBeginDocument{\@EveryShipoutACL@Init} 196 | 197 | %% ----- Set up for placing additional items into the submitted version --MM 198 | %% 199 | %% Based on eso-pic.sty 200 | %% 201 | %% Original available at: https://www.ctan.org/tex-archive/macros/latex/contrib/eso-pic 202 | %% Copyright (C) 1998-2002 by Rolf Niepraschk 203 | %% 204 | %% Which may be distributed and/or modified under the conditions of 205 | %% the LaTeX Project Public License, either version 1.2 of this license 206 | %% or (at your option) any later version. The latest version of this 207 | %% license is in: 208 | %% 209 | %% http://www.latex-project.org/lppl.txt 210 | %% 211 | %% and version 1.2 or later is part of all distributions of LaTeX version 212 | %% 1999/12/01 or later. 213 | %% 214 | %% In contrast to the original, we do not include the definitions for/using: 215 | %% gridpicture, div[2], isMEMOIR[1], gridSetup[6][], subgridstyle{dotted}, labelfactor{}, gap{}, gridunitname{}, gridunit{}, gridlines{\thinlines}, subgridlines{\thinlines}, the {keyval} package, evenside margin, nor any definitions with 'color'. 216 | %% 217 | %% These are beyond what is needed for the NAACL style. 218 | %% 219 | \newcommand\LenToUnit[1]{#1\@gobble} 220 | \newcommand\AtPageUpperLeft[1]{% 221 | \begingroup 222 | \@tempdima=0pt\relax\@tempdimb=\ESO@yoffsetI\relax 223 | \put(\LenToUnit{\@tempdima},\LenToUnit{\@tempdimb}){#1}% 224 | \endgroup 225 | } 226 | \newcommand\AtPageLowerLeft[1]{\AtPageUpperLeft{% 227 | \put(0,\LenToUnit{-\paperheight}){#1}}} 228 | \newcommand\AtPageCenter[1]{\AtPageUpperLeft{% 229 | \put(\LenToUnit{.5\paperwidth},\LenToUnit{-.5\paperheight}){#1}}} 230 | \newcommand\AtPageLowerCenter[1]{\AtPageUpperLeft{% 231 | \put(\LenToUnit{.5\paperwidth},\LenToUnit{-\paperheight}){#1}}}% 232 | \newcommand\AtPageLowishCenter[1]{\AtPageUpperLeft{% 233 | \put(\LenToUnit{.5\paperwidth},\LenToUnit{-.96\paperheight}){#1}}} 234 | \newcommand\AtTextUpperLeft[1]{% 235 | \begingroup 236 | \setlength\@tempdima{1in}% 237 | \advance\@tempdima\oddsidemargin% 238 | \@tempdimb=\ESO@yoffsetI\relax\advance\@tempdimb-1in\relax% 239 | \advance\@tempdimb-\topmargin% 240 | \advance\@tempdimb-\headheight\advance\@tempdimb-\headsep% 241 | \put(\LenToUnit{\@tempdima},\LenToUnit{\@tempdimb}){#1}% 242 | \endgroup 243 | } 244 | \newcommand\AtTextLowerLeft[1]{\AtTextUpperLeft{% 245 | \put(0,\LenToUnit{-\textheight}){#1}}} 246 | \newcommand\AtTextCenter[1]{\AtTextUpperLeft{% 247 | \put(\LenToUnit{.5\textwidth},\LenToUnit{-.5\textheight}){#1}}} 248 | \newcommand{\ESO@HookI}{} \newcommand{\ESO@HookII}{} 249 | \newcommand{\ESO@HookIII}{} 250 | \newcommand{\AddToShipoutPicture}{% 251 | \@ifstar{\g@addto@macro\ESO@HookII}{\g@addto@macro\ESO@HookI}} 252 | \newcommand{\ClearShipoutPicture}{\global\let\ESO@HookI\@empty} 253 | \newcommand{\@ShipoutPicture}{% 254 | \bgroup 255 | \@tempswafalse% 256 | \ifx\ESO@HookI\@empty\else\@tempswatrue\fi% 257 | \ifx\ESO@HookII\@empty\else\@tempswatrue\fi% 258 | \ifx\ESO@HookIII\@empty\else\@tempswatrue\fi% 259 | \if@tempswa% 260 | \@tempdima=1in\@tempdimb=-\@tempdima% 261 | \advance\@tempdimb\ESO@yoffsetI% 262 | \unitlength=1pt% 263 | \global\setbox\@cclv\vbox{% 264 | \vbox{\let\protect\relax 265 | \pictur@(0,0)(\strip@pt\@tempdima,\strip@pt\@tempdimb)% 266 | \ESO@HookIII\ESO@HookI\ESO@HookII% 267 | \global\let\ESO@HookII\@empty% 268 | \endpicture}% 269 | \nointerlineskip% 270 | \box\@cclv}% 271 | \fi 272 | \egroup 273 | } 274 | \EveryShipoutACL{\@ShipoutPicture} 275 | \newif\ifESO@dvips\ESO@dvipsfalse 276 | \newif\ifESO@grid\ESO@gridfalse 277 | \newif\ifESO@texcoord\ESO@texcoordfalse 278 | \newcommand*\ESO@griddelta{}\newcommand*\ESO@griddeltaY{} 279 | \newcommand*\ESO@gridDelta{}\newcommand*\ESO@gridDeltaY{} 280 | \newcommand*\ESO@yoffsetI{}\newcommand*\ESO@yoffsetII{} 281 | \ifESO@texcoord 282 | \def\ESO@yoffsetI{0pt}\def\ESO@yoffsetII{-\paperheight} 283 | \edef\ESO@griddeltaY{-\ESO@griddelta}\edef\ESO@gridDeltaY{-\ESO@gridDelta} 284 | \else 285 | \def\ESO@yoffsetI{\paperheight}\def\ESO@yoffsetII{0pt} 286 | \edef\ESO@griddeltaY{\ESO@griddelta}\edef\ESO@gridDeltaY{\ESO@gridDelta} 287 | \fi 288 | 289 | 290 | %% ----- Submitted version markup: Page numbers, ruler, and confidentiality. Using ideas/code from cvpr.sty 2015. --MM 291 | 292 | \font\naaclhv = phvb at 8pt 293 | 294 | %% Define vruler %% 295 | 296 | %\makeatletter 297 | \newbox\aclrulerbox 298 | \newcount\aclrulercount 299 | \newdimen\aclruleroffset 300 | \newdimen\cv@lineheight 301 | \newdimen\cv@boxheight 302 | \newbox\cv@tmpbox 303 | \newcount\cv@refno 304 | \newcount\cv@tot 305 | % NUMBER with left flushed zeros \fillzeros[] 306 | \newcount\cv@tmpc@ \newcount\cv@tmpc 307 | \def\fillzeros[#1]#2{\cv@tmpc@=#2\relax\ifnum\cv@tmpc@<0\cv@tmpc@=-\cv@tmpc@\fi 308 | \cv@tmpc=1 % 309 | \loop\ifnum\cv@tmpc@<10 \else \divide\cv@tmpc@ by 10 \advance\cv@tmpc by 1 \fi 310 | \ifnum\cv@tmpc@=10\relax\cv@tmpc@=11\relax\fi \ifnum\cv@tmpc@>10 \repeat 311 | \ifnum#2<0\advance\cv@tmpc1\relax-\fi 312 | \loop\ifnum\cv@tmpc<#1\relax0\advance\cv@tmpc1\relax\fi \ifnum\cv@tmpc<#1 \repeat 313 | \cv@tmpc@=#2\relax\ifnum\cv@tmpc@<0\cv@tmpc@=-\cv@tmpc@\fi \relax\the\cv@tmpc@}% 314 | % \makevruler[][][][][] 315 | \def\makevruler[#1][#2][#3][#4][#5]{\begingroup\offinterlineskip 316 | \textheight=#5\vbadness=10000\vfuzz=120ex\overfullrule=0pt% 317 | \global\setbox\aclrulerbox=\vbox to \textheight{% 318 | {\parskip=0pt\hfuzz=150em\cv@boxheight=\textheight 319 | \cv@lineheight=#1\global\aclrulercount=#2% 320 | \cv@tot\cv@boxheight\divide\cv@tot\cv@lineheight\advance\cv@tot2% 321 | \cv@refno1\vskip-\cv@lineheight\vskip1ex% 322 | \loop\setbox\cv@tmpbox=\hbox to0cm{{\naaclhv\hfil\fillzeros[#4]\aclrulercount}}% 323 | \ht\cv@tmpbox\cv@lineheight\dp\cv@tmpbox0pt\box\cv@tmpbox\break 324 | \advance\cv@refno1\global\advance\aclrulercount#3\relax 325 | \ifnum\cv@refno<\cv@tot\repeat}}\endgroup}% 326 | %\makeatother 327 | 328 | 329 | \def\aclpaperid{***} 330 | \def\confidential{ACL 2017 Submission~\aclpaperid. Confidential Review Copy. DO NOT DISTRIBUTE.} 331 | 332 | %% Page numbering, Vruler and Confidentiality %% 333 | % \makevruler[][][][][] 334 | \def\aclruler#1{\makevruler[14.17pt][#1][1][3][\textheight]\usebox{\aclrulerbox}} 335 | \def\leftoffset{-2.1cm} %original: -45pt 336 | \def\rightoffset{17.5cm} %original: 500pt 337 | \ifaclfinal\else\pagenumbering{arabic} 338 | \AddToShipoutPicture{% 339 | \ifaclfinal\else 340 | \AtPageLowishCenter{\thepage} 341 | \aclruleroffset=\textheight 342 | \advance\aclruleroffset4pt 343 | \AtTextUpperLeft{% 344 | \put(\LenToUnit{\leftoffset},\LenToUnit{-\aclruleroffset}){%left ruler 345 | \aclruler{\aclrulercount}} 346 | \put(\LenToUnit{\rightoffset},\LenToUnit{-\aclruleroffset}){%right ruler 347 | \aclruler{\aclrulercount}} 348 | } 349 | \AtTextUpperLeft{%confidential 350 | \put(0,\LenToUnit{1cm}){\parbox{\textwidth}{\centering\naaclhv\confidential}} 351 | } 352 | \fi 353 | } 354 | 355 | %%%% ----- End settings for placing additional items into the submitted version --MM ----- %%%% 356 | 357 | %%%% ----- Begin settings for both submitted and camera-ready version ----- %%%% 358 | 359 | %% Title and Authors %% 360 | 361 | \newcommand\outauthor{ 362 | \begin{tabular}[t]{c} 363 | \ifaclfinal 364 | \bf\@author 365 | \else 366 | % Avoiding common accidental de-anonymization issue. --MM 367 | \bf Anonymous ACL submission 368 | \fi 369 | \end{tabular}} 370 | 371 | % Changing the expanded titlebox for submissions to 2.5 in (rather than 6.5cm) 372 | % and moving it to the style sheet, rather than within the example tex file. --MM 373 | \ifaclfinal 374 | \else 375 | \addtolength\titlebox{.25in} 376 | \fi 377 | % Mostly taken from deproc. 378 | \def\maketitle{\par 379 | \begingroup 380 | \def\thefootnote{\fnsymbol{footnote}} 381 | \def\@makefnmark{\hbox to 0pt{$^{\@thefnmark}$\hss}} 382 | \twocolumn[\@maketitle] \@thanks 383 | \endgroup 384 | \setcounter{footnote}{0} 385 | \let\maketitle\relax \let\@maketitle\relax 386 | \gdef\@thanks{}\gdef\@author{}\gdef\@title{}\let\thanks\relax} 387 | \def\@maketitle{\vbox to \titlebox{\hsize\textwidth 388 | \linewidth\hsize \vskip 0.125in minus 0.125in \centering 389 | {\Large\bf \@title \par} \vskip 0.2in plus 1fil minus 0.1in 390 | {\def\and{\unskip\enspace{\rm and}\enspace}% 391 | \def\And{\end{tabular}\hss \egroup \hskip 1in plus 2fil 392 | \hbox to 0pt\bgroup\hss \begin{tabular}[t]{c}\bf}% 393 | \def\AND{\end{tabular}\hss\egroup \hfil\hfil\egroup 394 | \vskip 0.25in plus 1fil minus 0.125in 395 | \hbox to \linewidth\bgroup\large \hfil\hfil 396 | \hbox to 0pt\bgroup\hss \begin{tabular}[t]{c}\bf} 397 | \hbox to \linewidth\bgroup\large \hfil\hfil 398 | \hbox to 0pt\bgroup\hss 399 | \outauthor 400 | \hss\egroup 401 | \hfil\hfil\egroup} 402 | \vskip 0.3in plus 2fil minus 0.1in 403 | }} 404 | 405 | % margins for abstract 406 | \renewenvironment{abstract}% 407 | {\centerline{\large\bf Abstract}% 408 | \begin{list}{}% 409 | {\setlength{\rightmargin}{0.6cm}% 410 | \setlength{\leftmargin}{0.6cm}}% 411 | \item[]\ignorespaces}% 412 | {\unskip\end{list}} 413 | 414 | %\renewenvironment{abstract}{\centerline{\large\bf 415 | % Abstract}\vspace{0.5ex}\begin{quote}}{\par\end{quote}\vskip 1ex} 416 | 417 | \RequirePackage{natbib} 418 | % for citation commands in the .tex, authors can use: 419 | % \citep, \citet, and \citeyearpar for compatibility with natbib, or 420 | % \cite, \newcite, and \shortcite for compatibility with older ACL .sty files 421 | \renewcommand\cite{\citep} % to get "(Author Year)" with natbib 422 | \newcommand\shortcite{\citeyearpar}% to get "(Year)" with natbib 423 | \newcommand\newcite{\citet} % to get "Author (Year)" with natbib 424 | 425 | 426 | % bibliography 427 | 428 | \def\@up#1{\raise.2ex\hbox{#1}} 429 | 430 | % Don't put a label in the bibliography at all. Just use the unlabeled format 431 | % instead. 432 | \def\thebibliography#1{\vskip\parskip% 433 | \vskip\baselineskip% 434 | \def\baselinestretch{1}% 435 | \ifx\@currsize\normalsize\@normalsize\else\@currsize\fi% 436 | \vskip-\parskip% 437 | \vskip-\baselineskip% 438 | \section*{References\@mkboth 439 | {References}{References}}\list 440 | {}{\setlength{\labelwidth}{0pt}\setlength{\leftmargin}{\parindent} 441 | \setlength{\itemindent}{-\parindent}} 442 | \def\newblock{\hskip .11em plus .33em minus -.07em} 443 | \sloppy\clubpenalty4000\widowpenalty4000 444 | \sfcode`\.=1000\relax} 445 | \let\endthebibliography=\endlist 446 | 447 | 448 | % Allow for a bibliography of sources of attested examples 449 | \def\thesourcebibliography#1{\vskip\parskip% 450 | \vskip\baselineskip% 451 | \def\baselinestretch{1}% 452 | \ifx\@currsize\normalsize\@normalsize\else\@currsize\fi% 453 | \vskip-\parskip% 454 | \vskip-\baselineskip% 455 | \section*{Sources of Attested Examples\@mkboth 456 | {Sources of Attested Examples}{Sources of Attested Examples}}\list 457 | {}{\setlength{\labelwidth}{0pt}\setlength{\leftmargin}{\parindent} 458 | \setlength{\itemindent}{-\parindent}} 459 | \def\newblock{\hskip .11em plus .33em minus -.07em} 460 | \sloppy\clubpenalty4000\widowpenalty4000 461 | \sfcode`\.=1000\relax} 462 | \let\endthesourcebibliography=\endlist 463 | 464 | % sections with less space 465 | \def\section{\@startsection {section}{1}{\z@}{-2.0ex plus 466 | -0.5ex minus -.2ex}{1.5ex plus 0.3ex minus .2ex}{\large\bf\raggedright}} 467 | \def\subsection{\@startsection{subsection}{2}{\z@}{-1.8ex plus 468 | -0.5ex minus -.2ex}{0.8ex plus .2ex}{\normalsize\bf\raggedright}} 469 | %% changed by KO to - values to get teh initial parindent right 470 | \def\subsubsection{\@startsection{subsubsection}{3}{\z@}{-1.5ex plus 471 | -0.5ex minus -.2ex}{0.5ex plus .2ex}{\normalsize\bf\raggedright}} 472 | \def\paragraph{\@startsection{paragraph}{4}{\z@}{1.5ex plus 473 | 0.5ex minus .2ex}{-1em}{\normalsize\bf}} 474 | \def\subparagraph{\@startsection{subparagraph}{5}{\parindent}{1.5ex plus 475 | 0.5ex minus .2ex}{-1em}{\normalsize\bf}} 476 | 477 | % Footnotes 478 | \footnotesep 6.65pt % 479 | \skip\footins 9pt plus 4pt minus 2pt 480 | \def\footnoterule{\kern-3pt \hrule width 5pc \kern 2.6pt } 481 | \setcounter{footnote}{0} 482 | 483 | % Lists and paragraphs 484 | \parindent 1em 485 | \topsep 4pt plus 1pt minus 2pt 486 | \partopsep 1pt plus 0.5pt minus 0.5pt 487 | \itemsep 2pt plus 1pt minus 0.5pt 488 | \parsep 2pt plus 1pt minus 0.5pt 489 | 490 | \leftmargin 2em \leftmargini\leftmargin \leftmarginii 2em 491 | \leftmarginiii 1.5em \leftmarginiv 1.0em \leftmarginv .5em \leftmarginvi .5em 492 | \labelwidth\leftmargini\advance\labelwidth-\labelsep \labelsep 5pt 493 | 494 | \def\@listi{\leftmargin\leftmargini} 495 | \def\@listii{\leftmargin\leftmarginii 496 | \labelwidth\leftmarginii\advance\labelwidth-\labelsep 497 | \topsep 2pt plus 1pt minus 0.5pt 498 | \parsep 1pt plus 0.5pt minus 0.5pt 499 | \itemsep \parsep} 500 | \def\@listiii{\leftmargin\leftmarginiii 501 | \labelwidth\leftmarginiii\advance\labelwidth-\labelsep 502 | \topsep 1pt plus 0.5pt minus 0.5pt 503 | \parsep \z@ \partopsep 0.5pt plus 0pt minus 0.5pt 504 | \itemsep \topsep} 505 | \def\@listiv{\leftmargin\leftmarginiv 506 | \labelwidth\leftmarginiv\advance\labelwidth-\labelsep} 507 | \def\@listv{\leftmargin\leftmarginv 508 | \labelwidth\leftmarginv\advance\labelwidth-\labelsep} 509 | \def\@listvi{\leftmargin\leftmarginvi 510 | \labelwidth\leftmarginvi\advance\labelwidth-\labelsep} 511 | 512 | \abovedisplayskip 7pt plus2pt minus5pt% 513 | \belowdisplayskip \abovedisplayskip 514 | \abovedisplayshortskip 0pt plus3pt% 515 | \belowdisplayshortskip 4pt plus3pt minus3pt% 516 | 517 | % Less leading in most fonts (due to the narrow columns) 518 | % The choices were between 1-pt and 1.5-pt leading 519 | \def\@normalsize{\@setsize\normalsize{11pt}\xpt\@xpt} 520 | \def\small{\@setsize\small{10pt}\ixpt\@ixpt} 521 | \def\footnotesize{\@setsize\footnotesize{10pt}\ixpt\@ixpt} 522 | \def\scriptsize{\@setsize\scriptsize{8pt}\viipt\@viipt} 523 | \def\tiny{\@setsize\tiny{7pt}\vipt\@vipt} 524 | \def\large{\@setsize\large{14pt}\xiipt\@xiipt} 525 | \def\Large{\@setsize\Large{16pt}\xivpt\@xivpt} 526 | \def\LARGE{\@setsize\LARGE{20pt}\xviipt\@xviipt} 527 | \def\huge{\@setsize\huge{23pt}\xxpt\@xxpt} 528 | \def\Huge{\@setsize\Huge{28pt}\xxvpt\@xxvpt} 529 | -------------------------------------------------------------------------------- /report/report.tex: -------------------------------------------------------------------------------- 1 | % 2 | % File acl2017.tex 3 | % 4 | %% Based on the style files for ACL-2015, with some improvements 5 | %% taken from the NAACL-2016 style 6 | %% Based on the style files for ACL-2014, which were, in turn, 7 | %% based on ACL-2013, ACL-2012, ACL-2011, ACL-2010, ACL-IJCNLP-2009, 8 | %% EACL-2009, IJCNLP-2008... 9 | %% Based on the style files for EACL 2006 by 10 | %%e.agirre@ehu.es or Sergi.Balari@uab.es 11 | %% and that of ACL 08 by Joakim Nivre and Noah Smith 12 | 13 | \documentclass[12pt,a4paper]{article} 14 | \usepackage[hyperref]{acl2017} 15 | \usepackage{times} 16 | \usepackage{latexsym} 17 | \usepackage{amsmath} 18 | \usepackage{url} 19 | \usepackage{graphicx} 20 | \usepackage{amssymb} 21 | \usepackage[noend]{algpseudocode} 22 | \usepackage{algorithmicx,algorithm} 23 | \usepackage{float} 24 | 25 | \newenvironment{figurehere} {\def\@captype{figure}} {} 26 | 27 | \aclfinalcopy % Uncomment this line for the final submission 28 | %\def\aclpaperid{***} % Enter the acl Paper ID here 29 | 30 | %\setlength\titlebox{5cm} 31 | % You can expand the titlebox if you need extra space 32 | % to show all the authors. Please do not make the titlebox 33 | % smaller than 5cm (the original size); we will check this 34 | % in the camera-ready version and ask you to change it back. 35 | 36 | \newcommand\BibTeX{B{\sc ib}\TeX} 37 | 38 | \title{Final Project: Smart Gomoku Agent} 39 | 40 | \author{Tianxiao Hu \\ 41 | School of Computer Science\\ 42 | Fudan University\\ 43 | {\tt txhu14}\\{\tt @fudan.edu.cn} \\\And 44 | Hui Xu \\ 45 | School of Data Science\\ 46 | Fudan University\\ 47 | {\tt xuhui14}\\{\tt @fudan.edu.cn} \\\And 48 | Bing Zhang \\ 49 | School of Data Science\\ 50 | Fudan University\\ 51 | {\tt bingzhang14}\\{\tt @fudan.edu.cn} 52 | } 53 | 54 | \date{\today} 55 | 56 | \begin{document} 57 | \maketitle 58 | \begin{abstract} 59 | Our Gomoku game supports two types of games, including HUMAN VS AI and AI VS AI. A well-designed user interface is provided and 3 versions of AI algorithms are implemented: \textbf{Greedy}, \textbf{Minimax Search} and \textbf{Monte Carlo Tree Search}. For source code used in the project, please visit our code respository\footnote{https://github.com/TianxiaoHu/GomokuAgent}. 60 | \end{abstract} 61 | 62 | \section{Introduction} 63 | Gomoku, also called ``five in a row'', is a board game which originates from Japan. It is a game played on a Go board typically of size 15 x 15. In the game, players will take turns placing pieces until a player has managed to connect 5 in a row. 64 | 65 | \begin{figure}[!h] 66 | \centering\includegraphics[width=2.5in]{2.png} 67 | \caption{A Typical Gomoku Game} 68 | \end{figure} 69 | 70 | This project is aimed to develop a smart agent for Gomoku game. We use 3 main strategies to implement the agent: \textbf{Greedy}, \textbf{Minimax Search} and \textbf{Monte Carlo Tree Search(MCTS)}. We will introduce them in details in the following sections.\\ 71 | We also developed a user interface for Gomoku game. Special thanks to open source project gobang\footnote{https://github.com/lihongxun945/gobang} for the beautiful design of the web page! 72 | 73 | \section{Evaluation Function} 74 | First of all, in order to quantify the properties of the current situation, we construct an evaluation function to estimate the win-probability. And naturally, the evaluation function will incorporate a great deal of the knowledge about the Gomoku game. Therefore, the evaluation can be either naive or complicated, which depends on the your understanding towards the Gomoku game. In general, the more complex the evaluation is, the slower the program will get over time. 75 | 76 | In this section, we mainly propose two versions of the evaluation function. 77 | 78 | \subsection{The First Version} 79 | The general idea of the first version is fairly simple. As is known to all, the object of the game is to be the first player to achieve five pieces in a row, horizontally, vertically or diagonally. Therefore, we can focus only on the five continuous positions on the chess board, hereinafter called ``five-tuple''. Generally, the chess board is $15\times 15$, having 572 five-tuples in all. Then we can assign a score to every five-tuple based on the number of the black and white pieces in it. Here we ignore the relative position of pieces. Then the evaluation to a given situation is the sum of the score of all 572 five-tuples. Suppose we take piece "x" and opponent takes piece "o", then the score table for me is displayed as follow, 80 | \begin{table}[h] 81 | \centering 82 | \begin{tabular}{c|c} 83 | \hline 84 | Five-Tuple&Score \\ 85 | \hline 86 | x&15\\ 87 | xx&400\\ 88 | xxx&1800\\ 89 | xxxx&100000\\ 90 | xxxxx&10000000\\ 91 | o&-35\\ 92 | oo&-800\\ 93 | ooo&-15000\\ 94 | oooo&-800000\\ 95 | ooooo&-10000000\\ 96 | Blank&7\\ 97 | Polluted&0\\ 98 | \hline 99 | \end{tabular} 100 | \end{table} 101 | 102 | \noindent\begin{small}\emph{Note the score to blank five-tuple is 7 rather than 0. This is because there is worse condition, where the five-tuple is polluted, i.e., both black and white pieces in the tuple.}\end{small} 103 | 104 | The first version of the evaluation function is fairly simple and works rapidly. Nevertheless, after trying dozens of man-machine games, we find the agent can make some stupid mistakes, which result from its excessively simple evaluation function. Actually, the defect can be well solved by applying Minimax algorithm, but at a high price of the computing resource. 105 | \subsection{The Second Version} 106 | Then we intend to add more knowledge to the evaluation function to make it ``smarter''. 107 | 108 | In the second version of the evaluation function, we take more account of chess-types, or in other words, the relative position of the pieces. 109 | 110 | For every non-blank position on the chess board, we take it as the center and extend four positions to both sides horizontally, vertically and diagonally. Then we can obtain four nine-tuples. For each nine-tuple, we check its chess-type and assign a score according to the new score table. Add up these four scores and we can get the score of this position. 111 | 112 | Finally, our score is the sum of all positions occupied by our pieces while the opponent's score is the sum of all positions occupied by his pieces. And the evaluation to current situation is the difference of our score and opponent's score. 113 | 114 | The new score table is stated below, 115 | \begin{table}[h] 116 | \centering 117 | \begin{tabular}{c|c|c} 118 | \hline 119 | Chess-Type&Self-Score&Opp-Score \\ 120 | \hline 121 | Five&1000000&1000000\\ 122 | Alive-Four&20000&100000\\ 123 | CoDash-Four&6100&65000\\ 124 | GapDash-Four&6000&65000\\ 125 | CoAlive-Three&1100&5500\\ 126 | GapAlive-Three&1000&5000\\ 127 | CoAsleep-Three&300&200\\ 128 | GapAsleep-Three&290&200\\ 129 | TeAsleep-Three&290&200\\ 130 | FalseAlive-Three&290&200\\ 131 | Alive-Two&100&90\\ 132 | Asleep-Two&10&9\\ 133 | One&3&4\\ 134 | NoThreat&1&1\\ 135 | \hline 136 | \end{tabular} 137 | \end{table} 138 | 139 | \noindent\begin{small}\emph{Note the names of the chess-types are fabricated by ourselves because we can't find accurate translation to these terms. If you are interest in the exact chess mode corresponding to the chess-types, please refer to the code or contact us.}\end{small} 140 | 141 | \subsection{Using Genetic Algorithm to find Better Evaluation Function} 142 | You may find in the score table above, the chess-types and corresponding self-scores and opponent scores are quite subjective. Thus, we apply genetic algorithm to find a better evaluation function, which is inspired by the process of natural selection\cite{ml}. 143 | 144 | We initialize the algorithm by setting the original score table as the very first generation. Afterwards, it can breed a new generation by randomly plus or minus 3\% specific chess-type score. The parent agent will play with each child agent 100 times and find the best children as the new parent. When the new generation can't defeat their parent, the algorithm comes to an end. 145 | 146 | The results are as follow: 147 | \begin{table}[h] 148 | \centering 149 | \begin{tabular}{c|c|c} 150 | \hline 151 | Chess-Type&Self-Score&Opp-Score \\ 152 | \hline 153 | Five&1229874&1304773\\ 154 | Alive-Four&27685&119405\\ 155 | CoDash-Four&8198&84810\\ 156 | GapDash-Four&7164&75353\\ 157 | CoAlive-Three&1202&7176\\ 158 | GapAlive-Three&1426&6720\\ 159 | CoAsleep-Three&415&219\\ 160 | GapAsleep-Three&378&239\\ 161 | TeAsleep-Three&336&253\\ 162 | FalseAlive-Three&357&246\\ 163 | Alive-Two&127&111\\ 164 | Asleep-Two&11&12\\ 165 | One&4&5\\ 166 | NoThreat&1&1\\ 167 | \hline 168 | \end{tabular} 169 | \end{table} 170 | 171 | 172 | \section{Greedy Algorithm} 173 | Greedy algorithm is an algorithmic paradigm that follows the problem solving heuristic of making the locally optimal choice at each stage, with the hope of finding a global optimum. 174 | 175 | For our Gomoku agent, the evaluation function is exactly the so-called problem solving heuristic. 176 | And the implementation of the greedy search is elaborated as follows: 177 | \begin{algorithm}[h] 178 | \caption{Greedy Search} 179 | \hspace*{0.02in} {\bf Input:} 180 | chess board\\ 181 | \hspace*{0.02in} {\bf Output:} 182 | position of move 183 | \begin{algorithmic} 184 | \For{each position (i, j) on the chess board} 185 | 閵嗏偓閵嗏偓\If{position (i, j) is blank} 186 | 閵嗏偓閵嗏偓閵嗏偓閵嗏偓\State suppose place my piece on (i, j) 187 | \State calculate my score: myScore 188 | \State calculate opponent score: opScore 189 | \State score(i, j) = myScore - opScore 190 | \EndIf 191 | \EndFor 192 | \Return the position with the max score 193 | \end{algorithmic} 194 | \end{algorithm} 195 | 196 | However, after dozens of games, we find the greedy strategy does not in general produce an optimal solution. On the one hand, owing to the limitation of the evaluation function, the evaluated score of the current situation can't describe the win-probability precisely. On the other hand, this is also the inherent defect of the greedy algorithm. But nonetheless a greedy heuristic may yield locally optimal solutions that approximate a global optimal solution in a reasonable time, which is appealing when solving many other problems. 197 | 198 | 199 | \section{Learn Openings from Game Record} 200 | After testing our agents for a number of games, we found opening is fairly important for Gomoku. If our agent is playing with a experienced human, it will easily lose the advantage during the opening. However, there are hundreds of different openings and they are hard to compute or collect. To solve the problem, we let our agent learn from 5581 top-level game records\footnote{http://game.onegreen.net/Soft/HTML/47233.html}. If current game matches some sequences of initial moves in the game records, it will compute scores for records and find the next step having the highest score. 201 | 202 | \section{Minimax Algorithm} 203 | 204 | \subsection{Introduction to Minimax and Alpha-Beta Prunning} 205 | 206 | In general games, the maximin value of a player is the largest value that the player can be sure to get without knowing the actions of the other players; equivalently, it is the smallest value the other players can force the player to receive when they know his action\cite{ai}. 207 | 208 | Calculating the maximin value of a player is done in a worst-case approach: for each possible action of the player, we check all possible actions of the other players and determine the worst possible combination of actions - the one that gives player $i$ the smallest value. Then, we determine which action player $i$ can take in order to make sure that this smallest value is the largest possible. 209 | 210 | \begin{figure}[H] 211 | \centering\includegraphics[width=2in]{3.png} 212 | \caption{Example: Minimax Search} 213 | \end{figure} 214 | 215 | In Gomoku game, we use \textbf{evaluation function} to score each chess board. After a player has placed a piece on the chess board, a new state is created right after the old. Afterwards, agents can search the tree and return the best choice. However, due to the large search space of Gomoku, the tree will expand rapidly and lead to a unbearable waiting time. We employed Alpha-Beta Prunning to accelerate our agent. 216 | 217 | Alpha-Beta pruning is a search algorithm that seeks to decrease the number of nodes that are evaluated by the Minimax algorithm in its search tree. It stops completely evaluating a move when at least one possibility has been found that proves the move to be worse than a previously examined move. Such moves need not be evaluated further. 218 | 219 | \begin{figure}[!h] 220 | \centering\includegraphics[width=3.2in]{4.png} 221 | \caption{Example: Minimax Search} 222 | \end{figure} 223 | 224 | Our pseudocode is listed as right-above. 225 | \begin{small} 226 | \begin{algorithm}[!h] 227 | \caption{Alpha-Beta Pruning algirithm} 228 | \hspace*{0.02in} {\bf function} 229 | alphabeta(node, depth, $\alpha, \beta$, maximizingPlayer) 230 | \begin{algorithmic} 231 | \If{depth = 0 or node is a terminal node} 232 | \State \Return the heuristic value of node 233 | \If{maxmizingPlayer} 234 | \State v $\gets - \infty$ 235 | \For{each child of node} 236 | \State v $\gets$ max(v, alphabeta(child, depth - 1, $\alpha$, $\beta$, FALSE) 237 | \State $\alpha \gets max(\alpha, v)$ 238 | \If{$\beta \leq \alpha$} 239 | \State break 240 | \EndIf 241 | \State \Return v 242 | \EndFor 243 | \Else 244 | \State $v \gets + \infty$ 245 | \For{each child of node} 246 | \State v $\gets$ min(v, alphabeta(child, depth - 1, $\alpha$, $\beta$, TRUE) 247 | \State $\beta \gets max(\beta, v)$ 248 | \If{$\beta \leq \alpha$} 249 | \State break 250 | \EndIf 251 | \State \Return v 252 | \EndFor 253 | \EndIf 254 | \EndIf 255 | \end{algorithmic} 256 | \end{algorithm} 257 | \end{small} 258 | 259 | \subsection{Speed Optimization} 260 | The depth of search tree is an important parameter in Minimax Search. If layers searched are not enough, our agent will be really short-sighted. However, for the sake of Python's efficiency, our naive implementation can only search for 2 layers in 1 second. If we force it to search 4 layers, the waiting time will become 10 seconds. Further optimization is needed. 261 | 262 | We use several methods to accelerate our program: 263 | 264 | \begin{itemize} 265 | \item \textbf{Less Search States}\\ 266 | We explore less search state for each player. Instead of search all possible place for a piece, we only consider places near pieces which are already placed in the chess board. 267 | \item \textbf{Zobrist Hashing States in Memory}\\ 268 | During the expanding of the search tree, some nodes may share the same state. Their scores will be the same so we need not calculate them twice. We save each state and its score in memory to accelerate the agent.\\ 269 | However, a state is hard to perform hashing. We use Zobrist Hashing to represent states. Zobrist Hashing starts by randomly generating bitstrings for each possible element of a board game, i.e. for each combination of a piece and a position. Now any board configuration can be broken up into independent position components, which are mapped to the random bitstrings generated earlier. The final Zobrist Hashing is computed by combining those bitstrings using bitwise XOR. When updating positions, rather than computing the hash for the entire chess board every time, the hash value of a chess board can be updated simply by XOR out the bitstring for positions that have changed, and XOR in the bitstrings for the new positions. 270 | \item \textbf{Python's Numba Package}\\ 271 | Our implementation is based on Python's scientific computing packages Numpy. We use Numba package to accelerate it. Numba is an Open Source NumPy-aware optimizing compiler for Python. It uses the LLVM compiler infrastructure to compile Python to machine code. This optimized function runs 200 times faster than the interpreted original function on a long NumPy array; and it is 30\% faster than NumPy's builtin \emph{sum()} function\footnote{https://en.wikipedia.org/wiki/Numba}. 272 | 273 | \end{itemize} 274 | 275 | Our optimized Alpha-Beta Prunning can search 8 layers within 1 second. And it turned out to be really hard to defeat especially when the agent goes first. 276 | 277 | \section{Monte Carlo Tree Search} 278 | \subsection{Introduction to MCTS and UCT} 279 | \par Monte Carlo Tree Search (MCTS) is a tree search technique expanding the search tree based on random sampling of the search space. The application of Monte Carlo tree search in games is based on many playouts. In each playout, the game is played out to the very end by selecting moves at random. The final game result of each playout is then used to weight the nodes in the game tree so that better nodes are more likely to be chosen in 280 | future playouts. The strategy is going to have to balance playing all of the machines to gather that information, with concentrating the plays on the observed best machine. One strategy, called UCB1, does this by constructing statistical confidence intervals for each machine. 281 | 282 | \begin{displaymath} 283 | \bar{x}_i \pm \sqrt{\frac{C\ln n}{n_i}} 284 | \end{displaymath} 285 | \begin{center} 286 | $\bar{x}_i$: the mean playout for machine \emph{i}\\ 287 | $n_i$: the number of plays of machine \emph{i} \\ 288 | $n$: the total number of plays 289 | \end{center} 290 | 291 | Then, the strategy is to pick the machine with the highest upper bound each time. Upper Confidence bound applied to Trees (UCT) 292 | is MCTS with UCB strategy. 293 | \par For Gomoku game, MCTS starts with a chess board and walk chess randomly until the end. The process is repeated many times which eliminates the best move for the current chess board. Each round of Monte Carlo tree search consists of four steps:\\ 294 | 295 | \begin{figurehere} 296 | \centering 297 | \includegraphics[width=90mm]{1.png} 298 | \end{figurehere} 299 | \begin{itemize} 300 | \item Selection: 301 | Build the root node based on the current chess board and generate all of its child nodes. The move to use would be chosen by the UCB1 algorithm and applied to obtain the next position to be considered. 302 | \item Expansion: 303 | Selection would then proceed until reach a position where not all of the child positions have statistics recorded. 304 | \item Simulation: 305 | If the node hasn't been simulated, then do a typical Monte Carlo simulation for chess game. Else, generate a random child node for the leaf node and do the simulation. 306 | \item Backpropagation: 307 | Update the reward of the simulation (generally 0 for lose and 1 for win) to the leaf node and its ancestor node. Meanwhile add the number of view for every node in the search path. 308 | \end{itemize} 309 | 310 | 311 | %Example: Monte Carlo Tree Search 312 | \par Repeat the playouts for many times until reach the search time or max search times and we can get the best move by selecting the maximum reward child node for the current root board. The pseudocode is showed as follow. 313 | \begin{algorithm}[H] 314 | \caption{The UCT algorithm} 315 | \hspace*{0.02in} {\bf function} 316 | UCTSearch($s_0$) 317 | \begin{algorithmic} 318 | \State create root node $v_0$ with state $s_0$ 319 | \While{within computational budget} 320 | \State $v_l \gets TreePolicy(v_0)$ 321 | \State $\Delta \gets DefaultPolicy(s(v_l))$ 322 | \State $BackUp(v_l, \Delta)$ 323 | \EndWhile 324 | \State \Return $a(BestChild(v_0, 0))$ 325 | \end{algorithmic} 326 | ~\\ 327 | \hspace*{0.02in} {\bf function} 328 | TreePolicy(v) 329 | \begin{algorithmic} 330 | \While{v is nonterminal} 331 | \If{v not fully expanded} 332 | \State \Return Expand(v) 333 | \Else 334 | \State $v \gets BestChild(v, Cp)$ 335 | \EndIf 336 | \EndWhile 337 | \State \Return v 338 | \end{algorithmic} 339 | ~\\ 340 | \hspace*{0.02in} {\bf function} 341 | Expand(v) 342 | \begin{algorithmic} 343 | \State choose a $\in$ untried actions from A(s(v)) 344 | \State add a new child v' to v with s(v') = f(s(v),a) and a(v') = a 345 | \State \Return v' 346 | \end{algorithmic} 347 | ~\\ 348 | \hspace*{0.02in} {\bf function} 349 | BestChild(v,c) 350 | \begin{algorithmic} 351 | \State \Return $\mathop{\arg\max} \frac{Q(v')}{N(v')} + c\sqrt{\frac{2lnN(v)}{N(v')}}$ 352 | \end{algorithmic} 353 | ~\\ 354 | \hspace*{0.02in} {\bf function} 355 | DefaultPolicy(s) 356 | \begin{algorithmic} 357 | \While{s is non-terminal} 358 | \State choose a $\in$ A(s) uniformly at random 359 | \State s $\gets$ f(s,a) 360 | \EndWhile 361 | \State \Return reward for state s 362 | \end{algorithmic} 363 | ~\\ 364 | \hspace*{0.02in} {\bf function} 365 | BackUp(v,$\Delta$) 366 | \begin{algorithmic} 367 | \While{v is not null} 368 | \State N(v) $\gets$ N(v) + 1 369 | \State Q(v) $\gets$ Q(v) + $\delta(v, p)$ 370 | \State v $\gets$ parent of v 371 | \EndWhile 372 | \end{algorithmic} 373 | \end{algorithm} 374 | \par Obviously the tree structure for a 15x15 Gomoku game chess board is too large, so the machine needs too many simulations for each choice of move and it take a long time for AI's move. Thus, the game experience is not very well. 375 | \par Since we cannot speed up our algorithm by using cluster environment, we conduct a 4-in-row game in a 7x7 game chess board. And here is a view for a game result. 376 | 377 | \begin{figure}[H] 378 | \centering\includegraphics[width=3in]{ai3.png} 379 | \caption{Result for MCTS algorithm} 380 | \end{figure} 381 | 382 | 383 | 384 | \section{Round Robin for Agents} 385 | Several agents are implemented and we hold a round robin for them. From the competition result we can make a detailed analysis on different strategies.\\ 386 | Information of agents is listed here: 387 | \begin{itemize} 388 | \item AI1: Greedy Algorithm with evaluation function version 1 389 | \item AI2: 2-layer Minimax Search with evaluation function version 1 390 | \item AI3: Monte Carlo Tree Search 391 | \item AI4: 8-layer Minimax Search with evaluation function version 1 392 | \item AI5: Greedy Algorithm with evaluation function version 2 393 | \item AI6 Greedy Algorithm with evaluation function version 1 but also known better openings 394 | \end{itemize} 395 | Note that AI3 runs quite slow and due to the large search space, it's performance is not satisfying. Besides, \textbf{Monte Carlo Tree Search} approach sometimes attempts to place a few pieces away from current pieces and build up ``combination moves'' that way, which is different from all other agents. 396 | \begin{itemize} 397 | \item \textbf{AI1 vs. AI2}\\ 398 | The difference is between Greedy Algorithm and 2-layer Minimax Search. That's to say, whether the agent can consider the opponent's next step matters. We find 2-layer Minimax Search has a slight advantage. If AI2 goes first, it will win all the 100 games. But if AI1 goes first, things become quite different. AI1 can win 14 games and 6 games turn out to be tie. AI2 still has 80\% winning percentage regardless of advantage of the upper edge. 399 | \item \textbf{AI1 vs. AI5}\\ 400 | The difference between the two is the evaluation function. Contrasted to AI1, the evaluation function of AI5 is more reasonable, considering more possible cases. If AI1 goes first, it will win 75\%games and if AI5 goes first, AI5 will win 89\% games. The competition result has relevance to the order for placing pieces. But in total, AI5 performs better. 401 | \item \textbf{AI2 vs. AI5}\\ 402 | When AI2 encounters with AI5, only one more layer for searching seems to be of no use. AI5 beats AI2 in each game and the result is independent of which AI goes first. 403 | \item \textbf{AI1 vs. AI6}\\ 404 | AI6 can learn next step from 5570 top-level game records. When AI6 goes first, it can beat AI1 easily in a short time. But when AI1 goes first, AI6 can only win 50\% games. The reason is that AI1 plays straight forward to carry out ``five in a row'' while AI6 will find a best place for latter pieces. 405 | \item \textbf{AI2 vs. AI6}\\ 406 | Interestingly, AI6 has a very high winning percentage(90\%) against AI2. We guess the reason is top-level openings can provide more opportunities for latter pieces. The games also end quickly, all of which less than 30 pieces in total. 407 | \item \textbf{AI5 vs. AI6}\\ 408 | After AI1 has learned better openings, it performs better in competitions with AI5. If AI6 goes first, it can win about 50\% games(AI1: 25\%). However, it nearly has nothing to do with the winning percent when AI5 goes first. The winning percent only rise from 11\% to 13\%. 409 | \item \textbf{AI4 vs. all other AI}\\ 410 | The depth of searching tree begins to dominate in the competitions of AI4. AI4 can easily beat any other AIs, even when AI4 is not the one goes first. \\ 411 | Although AI4's evaluation function is not the best, it can gain an advantage step by step. If other AI goes first, AI4 may not perform very well at the very beginning of the game(always be busy defending). However, after about 30 pieces are placed, the situation will become balanced. After about 50 pieces are placed, AI4 will win.\\ 412 | By the way, we invited several students in Fudan University that are good at Gomoku game to test our agents. According to their feedback, AI4 plays the best among all AIs especially AI4 goes first. ``It's hard to defeat'', one of the players told us. 413 | \end{itemize} 414 | 415 | 416 | % include your own bib file like this: 417 | \bibliographystyle{acl_natbib} 418 | \bibliography{ref} 419 | 420 | \end{document} 421 | --------------------------------------------------------------------------------