博主水平非常有限,欢迎巨佬们指正、建议!
2017年6月10日更新:
源码及数据库文件:https://github.com/daggerage/fun
背景
自前段时间不小心点错,升级了iOS10之后,就开始沉迷iMessage里的GamePigeon小游戏不能自拔。
CUP PONG有毒的操作,推推乐心机的决斗,都很棒。
然而这个叫“字谜”的游戏也真是紧张刺激…
游戏界面
游戏规则大概如上,6个字母,排出若干个单词,可以只用部分字母,但至少3个。
单词越长加分越高,1分钟内分数高者获胜。
这游戏运气好的时候能到5000多分,然而如果给的字母不太好拼的话,几百分、一千多也是经常的事…
然而这游戏似乎也没法作弊,用字典查还不如直接填上去来得快,反正填错了也不会扣分。
然后就萌生了能不能写个程序来代替我求解的想法。
思路
- 读取这6个字符,让它们做任意组合,再验证这个组合出来的单词存不存在。
- 验证的方式,可以是调用第三方库,可以是查询词典开放api,可以是将词典数据存到本地再做查询。
- 如果是单词,输出。
思路还是比较简单的,只是有一些实现上的细节问题。
- python或者java的相关第三方库我没找到。感觉应该不存在的,不用数据库怎么判断是否是合法单词?(jieba分词?没做更多了解)
- 词典开放api限制每小时查询次数为1000,而 A66=720 ,一次游戏就满了,还要换ip,速度也不能保证,麻烦。所以还是需要存到自己的数据库里。
- 全排列的算法..自己想的话还是有些头大,还是上网找找资料咯(太菜)。
实现及源码
经过将近一天的摸索踩坑,总算实现了:java+本地mysql
- 在网上找单词数据库,用MysqlWorkbench之类的GUI工具导入到本地数据库中(sql语句或txt、csv都行):在csdn上找到一个有大约1.5w单词的数据库(sql文件),由一个个的
INSERT
组成(找的这个版本很坑,竟然还有语法错误,需要手动改一下)。在数据库中建表,导入SQL。 - 输入长度为6的字符串,求出这6个单词的全排列,并利用HashSet求出全组合
- 查询数据库的单词与该HashSet的交集
- 输出
表结构,其实只用到ID和Word。(吐槽一下变量名竟然是拼音缩写,不过lx是啥?)
/*
* WordPuzzleSix.java
*/
import java.sql.*;
import java.util.*;
public class WordPuzzleSix {
private final int LENGTH =6;
private final int MIN_LENGTH=3;//单词最小长度
private Connection conn;//存储数据库连接
private HashSet<String> wordComb;//存储所有单词的组合
private ArrayList<String> resultNames;//存储所有结果单词String
private String chs;//存储输入字符串
//初始化
WordPuzzleSix(String chs){
if(chs.length()==LENGTH){
conn=getConn();
this.chs=chs;
wordComb=new HashSet<String>();
}
else{
System.out.println("number of characters less than 3!");
}
}
//获取数据库连接
private Connection getConn() {
String driver = "com.mysql.jdbc.Driver";//MySQL数据库驱动
String url = "jdbc:mysql://localhost:3306/fun?characterEncoding=utf-8&useSSL=false";//数据库路径,fun为数据库名
String username = "root";
String password = "";
try {
Class.forName(driver);//加载数据库驱动
conn = DriverManager.getConnection(url, username, password);//获取数据库连接
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
//关闭数据库连接
private void closeConn(){
try {
conn.close();
}catch (SQLException e){
e.printStackTrace();
}
}
private void swap(char[] s,int a,int b){
char temp;
temp=s[a];
s[a]=s[b];
s[b]=temp;
}
//递归求全排列
private void permuatation(char[] s,int start,int len) {
if (start > len-1) {
String word = "";
for (int i = 0; i < len; i++) {
word += s[i];
}
for (int i = MIN_LENGTH; i <= LENGTH; i++) {
wordComb.add(word.substring(0,i));//利用HashSet求全组合
}
}
else{
for (int i = start; i < len; i++) {
swap(s,start,i);
permuatation(s,start+1,len);
swap(s,start,i);
}
}
}
//构建SQL语句
private String formatSql(HashSet<String> hs){
String sql = "SELECT * FROM words WHERE word IN (";
int i=0;
for(String name:hs){
if(i==0){
sql+=String.format("'%s'",name);
}
else{
sql+=String.format(",'%s'",name);
}
i++;
}
sql+=")";
return sql;
}
private void printResult(){
System.out.println("#####"+chs+"#####");
int i=0;
for (String name:resultNames){
i++;
System.out.println(i+" "+name);
}
}
//主方法
public void findAllWords(){
//将读入的的String转成char[]
char[] c=new char[LENGTH];
for (int i = 0; i < LENGTH; i++) {
c[i]=chs.charAt(i);
}
permuatation(c,0, LENGTH);//进行递归全排列
resultNames=new ArrayList<String>();//记录最终结果
String sql=formatSql(wordComb);
try {
Statement st=conn.createStatement();
ResultSet rs=st.executeQuery(sql);//执行组合成的sql语句
//取出查询到的结果集,只取word字段的值
while (rs.next()){
String name=rs.getString("word");
resultNames.add(name);//添加到结果集中
}
}catch (SQLException e){
e.printStackTrace();
}
//对结果集按字符串长度,升序排列
resultNames.sort(new Comparator<String>() {
@Override //重写compare方法
public int compare(String o1, String o2) {
return o1.length() - o2.length();
}
});
printResult();
closeConn();//别忘了关闭连接
}
//一开始使用的方法,已废弃
private Word select(String word){
PreparedStatement pstm;
try {
String sql = "SELECT * FROM words WHERE word=?";
pstm=conn.prepareStatement(sql);
pstm.setString(1,word);
ResultSet rs = pstm.executeQuery();
if(rs.next()){
String wordName=rs.getString("word");
Word w=new Word(wordName);
return w;
}
}catch (SQLException e){
e.printStackTrace();
}
return null;
}
}
//Select方法用到的实体(Entity)类,已废弃
class Word{
public String word;
Word(String word){
this.word=word;
}
}
/*
* Main.java
*/
import java.util.Scanner;
public class Main {
//主程序
public static void main(String[] args) {
Scanner s=new Scanner(System.in);
while (true){
String input=s.nextLine();//读取输入字符串
if(!input.equals("quit")) {
if(input.length()==6){
long startTime=System.currentTimeMillis();
WordPuzzleSix wps = new WordPuzzleSix(input);
wps.findAllWords();
long endTime=System.currentTimeMillis();
long timeCost=endTime-startTime; //计算消耗时间
System.out.println("time cost : "+timeCost+" ms");
}
else{
System.out.println("number of characters less than 3!");
}
}
else{
break;
}
}
s.close();
}
}
最后结果:
总结
虽然程序很小很简单,但写一遍下来,也对之前不是很清楚的地方熟悉了很多吧。
用到的知识点:
- 数据库操作,定义(
DML
,DDL
) - JDBC连接、操作MySQL数据库,以及流程优化
- 全排列算法的递归实现与改动
ArrayList
,HashSet
以及ArrayList中sort
方法的使用(匿名内部类,方法重写)try...catch
异常处理- Java I/O
实现方向的选择:
- 为什么用Java:感觉用Python会方便一些,但因为Windows10下python-mysql的包难以配置(很久之前试过,放弃了),总是有各种版本问题,而java的jdbc已经有储备,就用了Java。python还是在linux下方便,而我总不会因为玩个字谜游戏切去linux吧
- 关于怎么查单词:可以看到,
select
方法和Word
实体类被抛弃了。其实一开始是用循环select
的方法进行查询,即一共要进行900次左右的select
,在只进行一次数据库连接(getConnection()
),不断开的情况下,做完这些查询耗时大约在17~60秒,速度太慢了,而且很不稳定,不知道为什么。所以最后采用了WHERE ... IN ()
的方式进行集合查询,耗时单位都改成毫秒了。得出结论,在数据库端做一条很复杂、数据很多的查询,也比反复做多条查询速度要快。由于对数据库的了解不深,背后具体的原理日后可能更新。
不足:
- 因为数据库比较小(跟游戏的相比),每次查出来的结果都不是很多,都输进去也就4000-6000分的样子,而且这游戏很鬼畜,一个名词,加s变复数后算一个新单词,而这个程序并没有选出复数、过去式等等(也不一定每局游戏都多个s),所以还是得手动+1s,以及考察对单词过去式的记忆~
- 项目结构可能不是特别好,稍显凌乱?
不过也就一个小功能,也差不多?如果有大佬能指点一番,不胜感激! - 自己太菜了,感觉还是写的比较慢
后记
最后实际效果还是蛮好的,胜率大幅提升23333
当然也不是无敌的,有时候还是感叹AI无法战胜人类啊~附一张对战截图(“我竟然输给了人类?!”)
其实是故意让的,总是赢的话,女票都不跟自己玩了(逃