Java实现HangMan自动答题程序-初涉AI
猜单词游戏(英文:Hangman,“上吊的人”之意)是一个双人游戏。一位玩家想一个字,另一位尝试猜该玩家所想的字中的每一个字母。
要猜的字以一列横线表示,让玩家知道该字有多少个字母。如果猜字的玩家猜中其中一个字母,另一位便须于该字母出现的所有位置上写上该字母。如果猜的字母没有于该字中出现,另一位玩家便会画吊颈公仔的其中一笔。游戏会在以下情况结束:
“我要t字。”“有, 在第八和第十一位。”
· 猜字的玩家猜完所有字母,或猜中整个字
· 另一位玩家画完整幅图:
这幅图其实是设计得像一个吊颈的人。尽管这幅图的品味曾经引起争议,却一直沿用至今。
这幅图的本质各有不同;有玩家在游戏开始前画绞刑架,在游戏期间画人的身体(传统上先画头,再画躯干,然后是左手、同右手,最后是左脚、右脚)。
有玩家在开始时不会画任何图,将绞刑架的每一笔当作游戏的一部分,这种做法实际上给予玩家更多机会。
历史
《牛津字母游戏指南》(The Oxford Guide to Word Games)(牛津大学出版社)作者东尼·欧加迪(Tony Augarde)说:“猜单词游戏的起源已经无从考究,但似是在维多利亚时代开始流行起来。”
爱丽丝·比加·歌美(Alice Bertha Gomme)于1894年出版的《传统游戏》(Traditional Games)中曾提及这游戏,书中称之为“雀、兽、鱼”(“Birds, Beasts and Fishes”)。规则很简单;一个玩家写下一种动物的首尾两个字母,由另外一位猜该字的中间部份。
其他出处中这游戏被称为“绞刑架”(Gallows)或“吊颈游戏”(The Game of Hanging)。
自动解题报告算法思路:
模拟人脑思考即初步设计AI 算法。
第一步:电脑随机出一个单词M,唯一传递出来的信息就是这个单词的位数,即字母的个数。
对应解决办法:那么根据这一点,首先在单词库中对所有的单词进行分析,并且以单词的位数进行分类,最后得到单词位数从1-N 的一个单词集合。后面的操作都是对这个单词集合的操作。那么这里我们得到集合 X,表示指定位数的单词集合。
第二步:猜第一个字母,以大脑来思考的话,那么就应该是在X集合中出现次数最多的字母。
对应解决办法:首先在X集合中找到所有可能出现的字母,然后依次对出现的字母做一个试探性的猜测性分析,即:我如果猜这个字母,那么会有多少个单词会被命中,最后找出命中单词最大的那么字母。
第三步:从第二步会得出一个字母A(假设) ,那么就有两种可能性,M包含这个字母,那么第四步,否则的话,在集合X 中删除所有包含字母A 的单词,缩小猜测的范围。然后继续第二步。
第四步: 在猜中第一个字母后,做一个全匹配删除,即假设单词M 为“hello”,字母A 为 小写‘e’,那么可以得出在第二个位置上有一个字母’e’,同样就可以将第二个位置上不含有字母’e’的单词全部删除,缩小范围。返回第二步。
算法优化
1.在每一次猜中一个字母之后就检测当前剩余的单词库X集合中的单词数,如果小于出错的次数,那么做一个遍历,可以肯定一定可以在指定的出错次数内猜出来。
2.将每一次猜中的字母记录下来,形成一个 Test 字符串,并每次都与单词库做一个匹配操作。
可能存在的更高效的优化:
对所有单词进行分析,找出单词之间在构词法之间的相同点。
详细解决步骤:
利用jsoup 从网上爬单词,组成单词库,这里爬的是四级词汇。
调用程序:
private void getToken() { File file = new File("doc.txt"); if(file.exists()){ return; } String[] str = { "ab", "bc", "cd", "df", "fh", "hl", "lo", "or", "rt", "tw", "wz" }; htmlHelp html = new htmlHelp(); for (int i = 0; i < str.length; i++) { String url = "http://cet.hjenglish.com/zt/sijicihui" + str[i] + "/"; String s = html.getContent(url, str[i]); if (s != null && !"".equals(s)) html.parseDocument(s); } html.writeFileByFileWriter("doc.txt", html.list); }这里爬的是沪江英语四级的单词汇,就借来用用学学,貌似没有侵权吧.......(*^__^*) 嘻嘻……
下面的具体的实现程序:
package html; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Vector; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; public class htmlHelp { public List<String> list=null; public String getContent(String url, String ch) { String s = null; try { Document doc = Jsoup.connect(url).get(); /* * 沪江英语的源码在这里wz 都相同,可是标题又不同,一个Bug * */ if(ch.equals("wz")) ch = "tw"; String cc = ch.toUpperCase(); Element title = doc.getElementById(cc); if(title!=null) s = title.toString(); } catch (IOException e) { e.printStackTrace(); } return s; } public void parseDocument(String str) { if(list==null) list = new ArrayList<String>(); int fromIndex=0, judge=0, judge2=0; String s = "<br />", tmp=""; while((judge = str.indexOf(s, fromIndex)) != -1) { if((judge2 = str.indexOf(s, judge+5))!=-1){ for(int i=judge+6; i < judge2; i++) tmp += str.charAt(i); list.add(tmp); fromIndex = judge2; tmp = ""; }else break; } } public void writeFileByFileWriter(String filePath, List<String> list2){ File file = new File(filePath); boolean result=true; if(!file.exists()){ try { result = file.createNewFile(); } catch (IOException e) { e.printStackTrace(); } } if(result) { synchronized (file) { try { FileWriter fw = new FileWriter(filePath); Iterator<String> it = list2.iterator(); while(it.hasNext()){ fw.write(it.next()+'\n'); } fw.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
在爬好了单词库,对单词库按单词的位数进行分析归类。
调用程序:
private void parseToken() { parseToken = new ParseToken(); parseToken.parseToken(); }
实现程序:
package tangman_01; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import FileHelp.fileHelp; public class ParseToken { public ArrayList<String> []tokenString=null; public List<String> allString = null; @SuppressWarnings("unchecked") public void parseToken(){ List<String> list = fileHelp.readFileByLines("doc.txt"); Iterator<String> it = list.iterator(); String token=null, tmp=null; tokenString = new ArrayList[20]; for(int i=0; i < tokenString.length; i++) tokenString[i] = new ArrayList<String>(); if(allString==null) allString = new ArrayList<String>(); while(it.hasNext()) { token=""; tmp = it.next(); int i; for(i=0; i < tmp.length(); i++){ /* * 沪江英语的又一个bug ,只有单词解释,却没有单词,之前在爬单词时没有发现, * 现在只有在这里做一个判断 * */ if(isChinese(tmp.charAt(i))) break; if(tmp.charAt(i)!= ' ') token += tmp.charAt(i); else break; } if(i!=0) { tokenString[i].add(token); allString.add(token); } } } public boolean isChinese(char c) { Character.UnicodeBlock ub = Character.UnicodeBlock.of(c); if (ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS || ub == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS || ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A || ub == Character.UnicodeBlock.GENERAL_PUNCTUATION || ub == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION || ub == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS) { return true; } return false; } }
在对单词按单词的位数归好类之后,现在就是随机生一个需要猜的字符串。
private String randomSelect() { int size = parseToken.allString.size(); Random rdl = new Random(); String str = parseToken.allString.get(rdl.nextInt(size)); return str; }
接下来做的事情就是自动判断程序:
package tangman_01; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import javax.swing.text.StyledEditorKit.ItalicAction; public class autoJudge { private String judgeString; // 随机出来的字符串 private List<String> wrongChar = null;// 收集猜错的字母 private List<String> correctChar = null;// 收集猜对的字母 private ParseToken parseToken = null;// 初始的涵盖单词位数的字符串集合 private List<String> nextAllString = null;// 结果筛选之后的字符串集合 private char[] test = null;// 测试字符串,记录猜对的字母和字母所在的位置 private int[] num = null; // 记录字母在字符串匹配中的个数和位置 private int count = 0;// 记录猜错的次数 最大为8; boolean resu = false;// 结果为true 即为猜出结果否则为没猜出来 // 传入需要匹配的字符串,和原始单词位数的集合 autoJudge(String str, ParseToken parseToken) { judgeString = str; this.parseToken = parseToken; } public void getResult() { correctChar = new ArrayList<String>(); wrongChar = new ArrayList<String>(); test = new char[judgeString.length() + 5]; for (int i = 0; i < judgeString.length() + 5; i++) { test[i] = ' '; } // 猜第一个字母; char c1 = getFirst(); // 判断是否正确,错误返回-1, 正确返回正确的位数,并更新num 数组 /* * 在一个单词中因为存在重复的字母出现,比如 hello,当猜字母 l 时,那么返回的结果就是2,num中记录2个l 所在的位置 */ int isHas = containKey(c1); // begin // 拿到第一个符合字母 if (isHas == -1) { count++;// 猜错计数器加 1 wrongChar.add(c1 + ""); // 将包含错误字母的单词全删除,更新所在位数的单词库 deleteDoc(c1); // 猜下一个字母 char c2 = getNext(); isHas = containKey(c2); if (isHas == -1) { /* 循环猜,直到找到第一个匹配的单词 */ while (isHas == -1) { count++; wrongChar.add(c2 + ""); // 单字符删除 deleteDoc2(c2); c2 = getNext(); isHas = containKey(c2); if (count == 8) break; } correctChar.add(c2 + ""); for (int i = 0; i < isHas; i++) { test[num[i]] = c2; } } else { correctChar.add(c2 + ""); for (int i = 0; i < isHas; i++) { test[num[i]] = c2; } } } else { correctChar.add(c1 + ""); for (int i = 0; i < isHas; i++) { test[num[i]] = c1; } nextAllString = parseToken.tokenString[judgeString.length()]; }// end if (count < 8) { // 猜对了第一个字母之后,做全匹配删除 deleteDoc3(); nextBack(); } if (resu) { System.out.println("随机出来的字符串是:" + judgeString); System.out.println("错误匹配的次数为:" + count); System.out.print(" 错误匹配的字符/字符串分别为:"); for (int i = 0; i < wrongChar.size(); i++) System.out.print(wrongChar.get(i) + " "); System.out.println(); System.out.print("正确匹配的字符为:"); for (int i = 0; i < correctChar.size(); i++) { System.out.print(correctChar.get(i) + " "); } System.out.println(); } } private void nextBack() { if (resu) return; if (count > 8) return; /* * getNext2() 函数会判断这一次取的字母不是已经在正确匹配字符串中的字母 */ char c = getNext2(); int isHas = containKey(c); if (isHas == -1) { count++; wrongChar.add(c + ""); deleteDoc2(c);// 单字符匹配删除 } else { correctChar.add(c + ""); // 更新test for (int i = 0; i < isHas; i++) { test[num[i]] = c; } deleteDoc3();// 全匹配删除 } // 剩余单词分析 if (!parseRemain() || !isSuccess()) { nextBack(); } else return; } // 测试输出结果----调试用 // private void put() { // System.out.println(judgeString); // for(int i=0; i < test.length; i++) // System.out.print(test[i]); // System.out.println(); // System.out.println(nextAllString.size()); // System.out.println("count = " + count); // Iterator<String> it = nextAllString.iterator(); // while(it.hasNext()){ // System.out.println(it.next()); // } // // } // 剩余的字符串分析,如果找到则返回true 否则返回false; /* * 如果剩余的字符小于还可以错误的次数则可以保证得出正确结果. * */ private boolean parseRemain() { if (nextAllString.size() <= (8 - count)) { Iterator<String> it = nextAllString.iterator(); while (it.hasNext()) { String tmp = it.next(); if (tmp.equals(judgeString)) { resu = true; System.out.println("_________________在剩余分析中找到结果是:" + tmp); return true; } else { wrongChar.add(tmp); count++; } } } return false; } private char getNext2() { boolean[] ch = new boolean[100]; Iterator<String> it = nextAllString.iterator(); // 统计出现的字母 while (it.hasNext()) { String tmp = it.next(); for (int i = 0; i < tmp.length(); i++) { // 剔除不常用的符号 比如'-' if (tmp.charAt(i) < 'A') break; ch[tmp.charAt(i) - 'A'] = true; } } // 拿到出现的字母中最大的单词包含量 int[] count = new int[100]; int max = 0, maxPos = -1; for (int i = 0; i < ch.length; i++) { if (ch[i]) { if (isExist((char) ('A' + i))) continue; count[i] = getNextCount((char) ('A' + i)); if (count[i] > max) { max = count[i]; maxPos = i; } } } char c = (char) (maxPos + 'A'); return c; } /* * 判断当前的单词是否已经出现过 * 出现过返回true * */ private boolean isExist(char c) { Iterator<String> it = correctChar.iterator(); while (it.hasNext()) { String tmp = it.next(); if (tmp.charAt(0) == c) return true; } return false; } // 匹配成功返回 true 否则 false; private boolean isSuccess() { int i; for (i = 0; i < judgeString.length(); i++) { if (judgeString.charAt(i) != test[i]) break; } if (i != judgeString.length()) return false; // 直接打印结果 System.out.print("当前分析中找到的结果__________:"); for (i = 0; i < test.length; i++) System.out.print(test[i]); System.out.println(); resu = true; return true; } /* * 全字符匹配删除 * */ private void deleteDoc3() { List<String> tmpString = new ArrayList<String>(); Iterator<String> it = nextAllString.iterator(); while (it.hasNext()) { String tmp = it.next(); int j; for (j = 0; j < test.length; j++) { if (test[j] != ' ') { if (tmp.charAt(j) != test[j]) break; } } if (j == test.length) { tmpString.add(tmp); } } nextAllString = tmpString; } /* * 单字符匹配删除 * */ private void deleteDoc2(char c2) { List<String> tmpString = new ArrayList<String>(); Iterator<String> it = nextAllString.iterator(); while (it.hasNext()) { String tmp = it.next(); int i; for (i = 0; i < tmp.length(); i++) { if (tmp.charAt(i) == c2) break; } if (i == tmp.length()) { tmpString.add(tmp); } } nextAllString = tmpString; } /* * 猜下一个可能出现的字母 * */ private char getNext() { boolean[] ch = new boolean[100]; Iterator<String> it = nextAllString.iterator(); // 统计出现的字母 while (it.hasNext()) { String tmp = it.next(); for (int i = 0; i < tmp.length(); i++) { // 剔除不常用的符号 比如'-' if (tmp.charAt(i) < 'A') break; ch[tmp.charAt(i) - 'A'] = true; } } // 拿到出现的字母中最大的单词包含量 int[] count = new int[100]; int max = 0, maxPos = -1; for (int i = 0; i < ch.length; i++) { if (ch[i]) { count[i] = getNextCount((char) ('A' + i)); if (count[i] > max) { max = count[i]; maxPos = i; } } } char c = (char) (maxPos + 'A'); return c; } /* * 拿到字母在当前所有字符串中出现的个数 * */ private int getNextCount(char c) { Iterator<String> it = nextAllString.iterator(); int count = 0; while (it.hasNext()) { String tmp = it.next(); for (int i = 0; i < tmp.length(); i++) { if (tmp.charAt(i) == c) { count++; break; } } } return count; } private void deleteDoc(char c1) { nextAllString = new ArrayList<String>(); int length = judgeString.length(); Iterator<String> it = parseToken.tokenString[length].iterator(); while (it.hasNext()) { String tmp = it.next(); int i; for (i = 0; i < tmp.length(); i++) { if (tmp.charAt(i) == c1) break; } if (i == tmp.length()) { nextAllString.add(tmp); } } } /* * 判断猜的字母是否在匹配的字符串中,如果 * 存在那么返回存在的位数,否则返回-1 * */ private int containKey(char c1) { int tmp[] = new int[10]; int length = 0; int i; for (i = 0; i < judgeString.length(); i++) { if (judgeString.charAt(i) == c1) { tmp[length++] = i; } } num = tmp; if (length > 0) return length; else return -1; } /* * 第一次猜 * */ private char getFirst() { int length = judgeString.length(); boolean[] ch = new boolean[100]; Iterator<String> it = parseToken.tokenString[length].iterator(); while (it.hasNext()) { String tmp = it.next(); for (int i = 0; i < tmp.length(); i++) { // 剔除不常用的符号 比如'-' if (tmp.charAt(i) < 'A') break; ch[tmp.charAt(i) - 'A'] = true; } } int[] count = new int[100]; int max = 0, maxPos = -1; for (int i = 0; i < ch.length; i++) { if (ch[i]) { count[i] = getCount((char) ('A' + i)); if (count[i] > max) { max = count[i]; maxPos = i; } } } char c = (char) (maxPos + 'A'); return c; } private int getCount(char c) { int length = judgeString.length(); Iterator<String> it = parseToken.tokenString[length].iterator(); int count = 0; while (it.hasNext()) { String tmp = it.next(); for (int i = 0; i < tmp.length(); i++) { if (tmp.charAt(i) == c) { count++; break; } } } return count; } }
最后是一些其他的主调用程序:
main :
package tangman_01; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import FileHelp.fileHelp; import html.htmlHelp; public class main { public static void main(String[] args) { int size = 10; Start []start = new Start[size]; for(int i=0; i < size; i++) { start[i] = new Start(); start[i].startGame(); System.out.println("--------------------------"); } } }
start 类:
package tangman_01; import html.htmlHelp; import java.io.File; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Random; import FileHelp.fileHelp; public class Start { ParseToken parseToken = null; public void startGame() { // 拿到单词库 getToken(); // 分析单词库,即将单词以字母的位数做分析 parseToken(); // 生成的随机单词 String str = randomSelect(); autoJudge resu = new autoJudge(str, parseToken); resu.getResult(); } private String randomSelect() { int size = parseToken.allString.size(); Random rdl = new Random(); String str = parseToken.allString.get(rdl.nextInt(size)); return str; } private void parseToken() { parseToken = new ParseToken(); parseToken.parseToken(); } private void getToken() { File file = new File("doc.txt"); if(file.exists()){ return; } String[] str = { "ab", "bc", "cd", "df", "fh", "hl", "lo", "or", "rt", "tw", "wz" }; htmlHelp html = new htmlHelp(); for (int i = 0; i < str.length; i++) { String url = "http://cet.hjenglish.com/zt/sijicihui" + str[i] + "/"; String s = html.getContent(url, str[i]); if (s != null && !"".equals(s)) html.parseDocument(s); } html.writeFileByFileWriter("doc.txt", html.list); } }
文件操作类:
package FileHelp; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; public class fileHelp { public static List<String> readFileByLines(String filePath){ File file = new File(filePath); if(!file.exists() || !file.isFile()){ return null; } List<String> content = new ArrayList<String>(); try { FileInputStream fileInputStream = new FileInputStream(file); InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream, "GBK"); BufferedReader reader = new BufferedReader(inputStreamReader); String lineContent = ""; while ((lineContent = reader.readLine()) != null) { content.add(lineContent); } fileInputStream.close(); inputStreamReader.close(); reader.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return content; } }