我正在尝试学习人工智能以及如何在程序中实现它。最容易开始的地方可能是简单的游戏(在本例中为井字游戏)和游戏搜索树(递归调用;不是实际的数据结构)。我在有关该主题的讲座中发现了这个非常有用的视频。
我遇到的问题是对算法的第一次调用需要很长时间(大约 15 秒)才能执行。我已经在整个代码中放置了调试日志输出,看起来它调用了算法的某些部分的次数过多。
以下是为计算机选择最佳移动的方法:
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | Best myBest = new Best(); Best reply; if (prevScore == COMPUTER_WIN || prevScore == HUMAN_WIN || prevScore == DRAW){ myBest.score = prevScore; return myBest; } if (side == COMPUTER){ myBest.score = alpha; }else{ myBest.score = beta; } Log.d(TAG,"Alpha:" + alpha +" Beta:" + beta +" prevScore:" + prevScore); Move[] moveList = myBest.move.getAllLegalMoves(board); for (Move m : moveList){ String choice; if (side == HUMAN){ choice = playerChoice; }else if (side == COMPUTER && playerChoice.equals("X")){ choice ="O"; }else{ choice ="X"; } Log.d(TAG,"Current Move: column-" + m.getColumn() +" row-" + m.getRow()); int p = makeMove(m, choice, side); reply = chooseMove(!side, p, alpha, beta); undoMove(m); if ((side == COMPUTER) && (reply.score > myBest.score)){ myBest.move = m; myBest.score = reply.score; alpha = reply.score; }else if((side == HUMAN) && (reply.score < myBest.score)){ myBest.move = m; myBest.score = reply.score; beta = reply.score; }//end of if-else statement if (alpha >= beta) return myBest; }//end of for loop return myBest; } |
如果位置为空,则
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | //I'm unsure whether this method really belongs in this class or in the grid class, though, either way it shouldn't matter. items = 0; moveList = null; Move move = new Move(); for (int i = 0; i < 3; i++){ for(int j = 0; j < 3; j++){ Log.d(TAG,"At Column:" + i +" At Row:" + j); if(grid[i][j] == null || grid[i][j].equals("")){ Log.d(TAG,"Is Empty"); items++; if(moveList == null || moveList.length < items){ resize(); }//end of second if statement move.setRow(j); move.setColumn(i); moveList[items - 1] = move; }//end of first if statement }//end of second loop }//end of first loop for (int k = 0; k < moveList.length; k++){ Log.d(TAG,"Count:" + k +" Column:" + moveList[k].getColumn() +" Row:" + moveList[k].getRow()); } return moveList; } private void resize(){ Move[] b = new Move[items]; for (int i = 0; i < items - 1; i++){ b[i] = moveList[i]; } moveList = b; } |
总而言之:是什么导致我的决定,选择最佳行动,需要这么长时间?我错过了什么?有没有更简单的方法来实现这个算法?任何帮助或建议将不胜感激,谢谢!
一个带有 alpha beta 剪枝的极小极大树应该被可视化为一棵树,树的每个节点都是一个可能的移动,很多轮到未来,它的子节点是所有可以从中获取的移动。
为了尽可能快并保证您只需要与您正在向前看的移动数量呈线性关系的空间,您可以进行深度优先搜索并从一侧"扫描"到另一侧。例如,如果您想象正在构建整个树,那么您的程序实际上一次只会构建一个从引导到根的单链,并丢弃它完成的任何部分。
此时我将复制维基百科的伪代码,因为它真的非常简洁明了:
2 3 4 5 6 7 8 9 10 11 12 13 14 15 | if depth = 0 or node is a terminal node return score if Player = MaxPlayer for each child of node α := max(α, alphabeta(child, depth-1, α, β, not(Player) )) if β ≤ α break (* Beta cut-off *) return α else for each child of node β := min(β, alphabeta(child, depth-1, α, β, not(Player) )) if β ≤ α break (* Alpha cut-off *) return β |
注意事项:
-\\'for each child of node\\' - 与其编辑当前棋盘的状态,不如创建一个全新棋盘,它是应用移动的结果。通过使用不可变对象,您的代码将不太容易出现错误,并且总体上可以更快地进行推理。
-要使用此方法,请在当前状态下为您可以进行的所有可能移动调用它,将其深度 -1,-Infinity 用于 alpha,Infinity 用于 beta,并且应该从不动玩家开始\\轮到这些调用中的每一个 - 返回最高值的调用是最好的调用。
它在概念上非常非常简单。如果你编码正确,那么你永远不会一次实例化超过(深度)板,你永远不会考虑无意义的分支等等。
我不会为你分析你的代码,但是因为这是一个很好的编码 kata,所以我为井字游戏写了一个小 ai:
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 | public class Board { /** * -1: opponent * 0: empty * 1: player */ int[][] cells = new int[3][3]; /** * the best move calculated by eval(), or -1 if no more moves are possible */ int bestX, bestY; int winner() { // row for (int y = 0; y < 3; y++) { if (cells[0][y] == cells[1][y] && cells[1][y] == cells[2][y]) { if (cells[0][y] != 0) { return cells[0][y]; } } } // column for (int x = 0; x < 3; x++) { if (cells[x][0] == cells[x][1] && cells[x][1] == cells[x][2]) { if (cells[x][0] != 0) { return cells[x][0]; } } } // 1st diagonal if (cells[0][0] == cells[1][1] && cells[1][1] == cells[2][2]) { if (cells[0][0] != 0) { return cells[0][0]; } } // 2nd diagonal if (cells[2][0] == cells[1][1] && cells[1][1] == cells[0][2]) { if (cells[2][0] != 0) { return cells[2][0]; } } return 0; // nobody has won } /** * @return 1 if side wins, 0 for a draw, -1 if opponent wins */ int eval(int side) { int winner = winner(); if (winner != 0) { return side * winner; } else { int bestX = -1; int bestY = -1; int bestValue = Integer.MIN_VALUE; loop: for (int y = 0; y < 3; y++) { for (int x = 0; x < 3; x++) { if (cells[x][y] == 0) { cells[x][y] = side; int value = -eval(-side); cells[x][y] = 0; if (value > bestValue) { bestValue = value; bestX = x; bestY = y; if (bestValue == 1) { // it won't get any better, we might as well stop thinking break loop; } } } } } this.bestX = bestX; this.bestY = bestY; if (bestValue == Integer.MIN_VALUE) { // there were no moves left, it must be a draw! return 0; } else { return bestValue; } } } void move(int side) { eval(side); if (bestX == -1) { return; } cells[bestX][bestY] = side; System.out.println(this); int w = winner(); if (w != 0) { System.out.println("Game over!"); } else { move(-side); } } @Override public String toString() { StringBuilder sb = new StringBuilder(); char[] c = {'O', ' ', 'X'}; for (int y = 0; y < 3; y++) { for (int x = 0; x < 3; x++) { sb.append(c[cells[x][y] + 1]); } sb.append('\ '); } return sb.toString(); } public static void main(String[] args) { long start = System.nanoTime(); Board b = new Board(); b.move(1); long end = System.nanoTime(); System.out.println(new BigDecimal(end - start).movePointLeft(9)); } } |
精明的读者会注意到我没有使用 alpha/beta 截止值。尽管如此,在我有点过时的笔记本上,这在 0.015 秒内完成了游戏......
没有分析您的代码,我无法确定问题出在哪里。但是,您在搜索树的每个节点上记录每个可能的移动可能与它有关。
我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
总的来说,我对ruby还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru
我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t
我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚
Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack
在选择我想要运行操作的频率时,唯一的选项是“每天”、“每小时”和“每10分钟”。谢谢!我想为我的Rails3.1应用程序运行调度程序。 最佳答案 这不是一个优雅的解决方案,但您可以安排它每天运行,并在实际开始工作之前检查日期是否为当月的第一天。 关于ruby-如何每月在Heroku运行一次Scheduler插件?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/8692687/
我有一个对象has_many应呈现为xml的子对象。这不是问题。我的问题是我创建了一个Hash包含此数据,就像解析器需要它一样。但是rails自动将整个文件包含在.........我需要摆脱type="array"和我该如何处理?我没有在文档中找到任何内容。 最佳答案 我遇到了同样的问题;这是我的XML:我在用这个:entries.to_xml将散列数据转换为XML,但这会将条目的数据包装到中所以我修改了:entries.to_xml(root:"Contacts")但这仍然将转换后的XML包装在“联系人”中,将我的XML代码修改为
我有一大串格式化数据(例如JSON),我想使用Psychinruby同时保留格式转储到YAML。基本上,我希望JSON使用literalstyle出现在YAML中:---json:|{"page":1,"results":["item","another"],"total_pages":0}但是,当我使用YAML.dump时,它不使用文字样式。我得到这样的东西:---json:!"{\n\"page\":1,\n\"results\":[\n\"item\",\"another\"\n],\n\"total_pages\":0\n}\n"我如何告诉Psych以想要的样式转储标量?解