日本麻将记点器:日麻Saki

一个小小的日本麻将记点器,仅以陶冶节操(终于上班摸鱼摸出来了…)
之前看到NGA论坛上有位大神做了个日麻面基APP
,感觉有很多改进的空间,于是自己忍不住写了一个,很多界面和功能都借鉴了原版(没错,是借鉴!),代码就重新撸了一遍,和朋友一边玩一边完善功能,断断续续一年多了,功能都完成得差不多,上传来给大家分享一下。
不过功能太多,界面按钮有点小,如果用惯了以前大按钮的,可能会不适应,不喜勿喷!

本APP是免费开源软件,任何人可以免费使用、随意修改、传播该APP,但必须注明原作者。

核心代码简析

日本麻将主要涉及的核心就是如何算番算符,搞掂了这些问题,程序剩下的就可以随便搞了,下面来简单介绍一下方法。

1.和牌判定

首先,要实现自动算番,当然要先判断这副牌能不能和牌,这里使用最简答粗暴但是最快的方法:直接把所有和牌的样式列出来,然后来匹配[手动狗头]

/*** 索引方法*/
public class AgariIndex {static final int[] n_zero;static final TreeMap<Integer, int[]> tbl;static {n_zero = new int[34];Arrays.fill(n_zero, 0);tbl = new TreeMap<Integer, int[]>();init(tbl);}public static boolean done(List<Tile> tiles, int added, List<Group[]> groups) {        int[] n = null;int[] pos = new int[14];int[] ret = null;n = analyse(tiles);int key = calc_key(n, pos);ret = agari(key);  if (ret == null || ret.length == 0) {//          System.out.println("番名:混水摸鱼");return false;} else {for (int r : ret) {//              System.out.println("r=" + r);
//              if ((r&0x4000000) == 0x4000000) {//                  System.out.println("七对子");
//              } else if ((r&0x8000000) == 0x8000000) {//                  System.out.println("九莲宝灯");
//              } else if ((r&0x10000000) == 0x10000000) {//                  System.out.println("一气通贯");
//              } else if ((r&0x20000000) == 0x20000000) {//                  System.out.println("二杯口");
//              } else if ((r&0x40000000) == 0x40000000) {//                  System.out.println("一杯口");
//              } else if ((r&0x80000000) == 0x80000000) {//                  System.out.println("国士无双");
//              }if ((r&0x4000000) != 0x4000000 && (r&0x80000000) != 0x80000000) { // 七对子 和 国士无双 除外List<Group> tmpList = new ArrayList<Groups.Group>();boolean hasHePai = false;GroupState state = GroupState.MenQing;int value = pos[(r>>6)&0xF];
//                  System.out.println("雀頭=" + value);state = (hasHePai = (added == value)) ? GroupState.HePai : GroupState.MenQing;tmpList.add(new Groups.Pair(value, state));int num_kotsu = r&0x7;int num_shuntsu = (r>>3)&0x7;for (int i = 0; i < num_kotsu; i++) {value = pos[(r>>(10+i*4))&0xF];if (hasHePai) state = GroupState.MenQing;else state = (hasHePai = (added == value)) ? GroupState.HePai : GroupState.MenQing;tmpList.add(new Groups.Pung(value, state));
//                      System.out.println("刻子=" + value);}for (int i = 0; i < num_shuntsu; i++) {value = pos[(r>>(10+num_kotsu*4+i*4))&0xF];if (hasHePai) state = GroupState.MenQing;else state = (hasHePai = (added == value || added == value + 1 || added == value + 2)) ? GroupState.HePai : GroupState.MenQing;if (state == GroupState.HePai) {tmpList.add(new Groups.Junko(value, state, calc_addedIndex(added, value)));} else {tmpList.add(new Groups.Junko(value, state));}
//                      System.out.println("順子=" + value);}Group[] tmpArray = new Group[tmpList.size()];groups.add(tmpList.toArray(tmpArray));}      }return true;}                }private static int calc_addedIndex(int added, int value) {int addedIndex = -1;if (added == value) {addedIndex = 0;} else if (added == value + 1) {addedIndex = 1;} else if (added == value + 2) {addedIndex = 2;}return addedIndex;}private static int[] analyse(List<Tile> tiles) {int[] n = n_zero.clone();for (Tile i : tiles) {n[i.Value()]++;}return n;}private static int calc_key(int[] n, int[] pos) {int p = -1;int x = 0;int pos_p = 0; //boolean b = false; //// for (int i = 0; i < 3; i++) {for (int j = 0; j < 9; j++) {if (n[i * 9 + j] == 0) {if (b) {b = false;x |= 0x1 << p;p++;}} else {p++;b = true;pos[pos_p++] = i * 9 + j;switch (n[i * 9 + j]) {case 2:x |= 0x3 << p;p += 2;break;case 3:x |= 0xF << p;p += 4;break;case 4:x |= 0x3F << p;p += 6;break;}}}if (b) {b = false;x |= 0x1 << p;p++;}}// for (int i = JpnSetting.TON; i <= JpnSetting.CHU; i++) {if (n[i] > 0) {p++;pos[pos_p++] = i;switch (n[i]) {case 2:x |= 0x3 << p;p += 2;break;case 3:x |= 0xF << p;p += 4;break;case 4:x |= 0x3F << p;p += 6;break;}x |= 0x1 << p;p++;}}return x;}private static int[] agari(int key) {return tbl.get(key);}private static void init(TreeMap<Integer, int[]> tbl) {init1(tbl);init2(tbl);init3(tbl);init4(tbl);init5(tbl);}/* 这里列举太多了,具体省略 */private static void init1(TreeMap<Integer, int[]> tbl) {}private static void init2(TreeMap<Integer, int[]> tbl) {}private static void init3(TreeMap<Integer, int[]> tbl) {}private static void init4(TreeMap<Integer, int[]> tbl) {}private static void init5(TreeMap<Integer, int[]> tbl) {}
}
2.役种判断

其次,当然要可以根据一副可以和牌的牌来判断各种役,这里的核心就在这里了

/*** 所有役的基类*/
public abstract class Yaku implements Comparable<Yaku> {/*** 该役的顺序编号,影响显示顺序*/protected int mOrderIndex;/***  表示役的类型,参考YakuFlag*/protected int mFlags;public Yaku() {}public int orderIndex() {return mOrderIndex;}public int flags() {return mFlags;}/*** 第一层过滤器,判断顺子数量和刻子数量。返回true则通过,false则不通过* 对于特殊牌型,junkoCount和pungCount都等于0* @param junkoCount 顺子数量* @param pungCount 刻子数量* @return*/protected boolean FilterTest(int junkoCount, int pungCount) { return true; }/*** 第二层过滤器,判断牌种类(万[0],饼[1],索[2],字[3])的数量。后2个参数所统计的对象是groups* @param kindCountsFromTiles 对tiles的统计结果* @param kindCounts 包含雀头的统计结果,换言之是5个group的统计结果* @param kindCountsWithoutPair 不包含雀头,换言之是4个group的统计结果* @return */protected boolean FilterTest(int[] kindCountsFromTiles, int[] kindCounts, int[] kindCountsWithoutPair) { return true; }/*** 测试并将役名与番数添加至返回集合中。返回false表示不匹配该类负责的任何役* @param result 可修改的返回集合* @param tiles 当前手牌* @param groups 包含雀头、顺子、刻子、杠子。如果是特殊役,该值为null* @param env 环境flags* @return*/protected abstract boolean Test(Collection<YakuValue> result, ITiles tiles, IGroups groups, int env);/*** 拦截符数计算。若该方法返回大于0,则后续不会对符数进行任何计算* @param groups* @param env* @return*/protected int HookCalculateFu(IGroups groups, int env)  { return -1; }@Overridepublic int compareTo(Yaku other) {return this.mOrderIndex = other.mOrderIndex;}protected static boolean SelfWindEquals(MjWind wind, int env) {int val1 = ((int) env >> 10) & 0xf;switch (wind) {case East:if (val1 == 0x1) return true;case South:if (val1 == 0x2) return true;case West:if (val1 == 0x4) return true;case North:if (val1 == 0x8) return true;default:return false;}}}

当然,这个只是基类,但继承他后,就可以为所欲为了2333333

3.点数判断

这里就考日麻的基本知识了,当然是检查役满、番数、符数这些数值然后层层判断,当然一副牌可能有多种不同的役组合,当然是要选点数最大的啦…

public class Game {private Yaku[] Yakus = {// 普通役new Yaku01_LiZhi(), new Yaku02_YiFa(), new Yaku03_ZiMo(), new Yaku04_PingHe(), new Yaku05_QiangGang(),new Yaku06_YiPai(), new Yaku07_DuanYaoJiu(), new Yaku08_YiBeiKou(), new Yaku09_LingShangKaiHua(), new Yaku10_HaiDiLaoYue(), new Yaku11_HeDiMoYu(), new Yaku12_Dora(), new Yaku13_InDora(), new Yaku14_RedDora(), new Yaku15_DoubleLiZhi(), new Yaku16_QiDuiZi(), new Yaku17_HunQuanDaiYao(), new Yaku18_YiQiTongGuan(), new Yaku19_SanSeTongShun(), new Yaku20_SanSeTongKe(), new Yaku21_SanGangZi(), new Yaku22_DuiDuiHe(), new Yaku23_SanAnKe(), new Yaku24_XiaoSanYuan(), new Yaku25_HunLaoTou(), new Yaku26_ErBeiKou(), new Yaku27_ChunQuanDaiYaoJiu(), new Yaku28_HunYiSe(), new Yaku29_QingYiSe(), // 役满役new Yaku50_TianDiRen(), new Yaku51_DaCheLun(), new Yaku52_GuoShuiWuShuang(), new Yaku53_JiuLianBaiDeng(),new Yaku54_SiAnKe(), new Yaku55_DaSanYuan(), new Yaku56_SiGangZi(), new Yaku57_LvYiSe(), new Yaku58_ZiYiSe(),new Yaku59_QingLaoTou(), new Yaku60_XiaoSixi(), new Yaku61_DaSixi()         };//private Yaku[] SpecialYakus = { new Yaku16_QiDuiZi(), new Yaku52_GuoShuiWuShuang()};//private Yaku[] ExtraYakus = { new Yaku30_LiuJuManGuan(), new Yaku62_BaLianZhuang(), new Yaku63_LeiJiYiMan() };public Score getScore(ITiles tiles, List<IGroups> gList, int env) {List<Score> scores = GetScores(tiles, gList, env);Score result = scores.get(0);for (int i = 1; i < scores.size(); i++) {Score tmpScore = scores.get(i);if (ScoreSystem.compare(result, tmpScore) < 0) result = tmpScore;}return result;}private List<Score> GetScores(ITiles tiles, List<IGroups> gList, int env) {boolean isMenQing = (env & YakuEnvironment.MenQianQing) != 0;Yaku[] allYakus = isMenQing ? Yakus : getNoMenQingYakus(Yakus);List<Yaku> envYakus = new ArrayList<Yaku>();List<Yaku> nonEnvYakus = new ArrayList<Yaku>();divideYakuForEnv(allYakus, envYakus, nonEnvYakus);List<YakuValue> tempResult = new ArrayList<YakuValue>();for (Yaku y : envYakus) {y.Test(tempResult, null, null, env);}int[] kindCountsFromTiles = tiles.KindCountsFromTiles();List<Score> results = new ArrayList<Score>();for (IGroups groups : gList) {int[] kindCounts = groups.KindCounts();int[] kindCountsWithoutPair = groups.KindCountsWithoutPair();int junkoCount = groups.JunkoCount();int pungCount = groups.PungCount();             List<YakuValue> result = new ArrayList<YakuValue>(tempResult);for (Yaku yaku : nonEnvYakus) {if (yaku.FilterTest(junkoCount, pungCount) && yaku.FilterTest(kindCountsFromTiles, kindCounts, kindCountsWithoutPair)) {yaku.Test(result, tiles, groups, env);}}tryRemoveNormalYakus(result);int fu = CalculateFu(result, groups, env);results.add(new Score(fu, result));}              // 形如11223344556677也算七对子,因此即使牌面能拆分成若干group也不能排除特殊役boolean succ = false;for (Yaku y : nonEnvYakus) {if (!y.FilterTest(0, 0)) continue;if (!y.FilterTest(kindCountsFromTiles, null, null)) continue;succ = (y.Test(tempResult, tiles, null, env) && y instanceof SpecialYaku) || succ;}if (succ) {tryRemoveNormalYakus(tempResult);int fu = CalculateFu(tempResult, null, env);results.add(new Score(fu, tempResult));}return results;}/*** 计算符数,并进行进位处理* * @param yakuValues 表示当前成立的所有役,可能存在hook符数计算的役* @param groups* @param env* @return*/private int CalculateFu(List<YakuValue> yakuValues, IGroups groups, int env) {int fu = 20;for (YakuValue y : yakuValues) {if (y.getFullYaku() > 0) return 0;int hookFu = y.getSource().HookCalculateFu(groups, env);if (hookFu > 0) {return hookFu;}}// 所有从面子获得的符for (Group g : groups.AllGroups()) {fu += CalculateFu(g, env);}if ((env & YakuEnvironment.MenQianQing) != 0 && (env & YakuEnvironment.ZiMo) == 0) {fu += 10; // 门前清荣和}if ((env & YakuEnvironment.ZiMo) != 0) {fu += 2;}if (fu == 20 && groups.JunkoCount() == 4 && (env & YakuEnvironment.MenQianQing) == 0 && (env & YakuEnvironment.ZiMo) == 0) {fu = 30; // 非门前清平和形食和(30符)}return (fu + 9) / 10 * 10; //进位}      /*** 计算group的符数* 注意:该方法返回的符数未进行进位处理* * @param group* @param env* @return*/private int CalculateFu(Group group, int env) {if (group instanceof Pair) {int fu = 0;if (group.value() >= JpnSetting.TON) {int val = group.value();int val1 = ((int)env >> 6) & 0xf; // 场风int val2 = ((int)env >> 10) & 0xf; // 自风if (val == val1) fu += 2;if (val == val2) fu += 2;if (fu == 0 && group.value() >= JpnSetting.HAK) fu += 2;}if (group.state() == GroupState.HePai) fu += 2; // 单骑return fu;} else if (group.isPung()) {int addedWind = 0x01;int selfWind = (((int)env >> 10) & 0xf);switch (group.wind()) {case East:addedWind = addedWind << 0;break;case South:addedWind = addedWind << 1;break;case West:addedWind = addedWind << 2;break;case North:addedWind = addedWind << 3;break;default:break;}boolean isClosed = group.state() == GroupState.MenQing|| (group.state() == GroupState.HePai && addedWind == selfWind);int fu = 2;if (group instanceof Gang) fu *= 4;if (isClosed) fu *= 2;if (group.hasYaoJiu()) fu *= 2;return fu;} else { //group is Junkoif (group.state() == GroupState.HePai) {int value = group.value();switch (group.addedIndex()) {case 0: //边张if (value == 6 || value == 9 || value == 24) { // 789的顺子return 2;}break;case 1: //嵌张return 2;case 2: //边张if (value == 0 || value == 9 || value == 18) { // 123的顺子return 2;}break;default:break;}}return 0;}}     private Yaku[] getNoMenQingYakus(Yaku[] Yakus) {List<Yaku> mqYakus = new ArrayList<Yaku>();for (Yaku yaku : Yakus) {if ((yaku.flags() & YakuFlag.MenQianQing) == 0) {mqYakus.add(yaku);}}        Yaku[] result = new Yaku[mqYakus.size()];mqYakus.toArray(result);return result;}private void divideYakuForEnv(Yaku[] Yakus, List<Yaku> envYakus, List<Yaku> nonEnvYakus) {for (Yaku yaku : Yakus) {if ((yaku.flags() & YakuFlag.Environment) != 0) {envYakus.add(yaku);} else {nonEnvYakus.add(yaku);}}       }private void tryRemoveNormalYakus(List<YakuValue> result) {boolean isFullYaku = false;for (YakuValue yakuValue : result) {if (yakuValue.getFullYaku() > 0) {isFullYaku = true;break;}}if (isFullYaku) {for (Iterator<YakuValue> iterator = result.iterator(); iterator.hasNext();) {YakuValue yakuValue = (YakuValue) iterator.next();if (yakuValue.getFullYaku() == 0) {iterator.remove();}}}}}

简要界面介绍

主界面
本程序一共七大功能块:对战入口、历史战绩、计分器、排行榜、玩家管理、表情包、音频包

右下角还有两个小功能:役种、数表。没记性的朋友可以有空看看→_→

一.对战入口

在这里可以选择对局的玩家,四个头像可以自由选择玩家,左上角是随机抽牌功能,右上角是自动排序功能,看自己的喜好用;右滑页面可以设置其他对局条件,自己慢慢体会…

开始游戏后就可以把手机放在麻将台中间待命了,摇骰子请点中间,玩家基本按钮有:立直、自摸、荣和、点炮多家(点荣和上面的炸弹),这些都可以在玩家管理那里设置个人音效(素材我找的是雀魂的),立直后也可以播放bgm,剩下的自己探索吧…

二.历史战绩

记录历史的神圣的地方,以及那些不堪回首的往事…

三.计分器

最原始、最核心的功能,可以多用用!!!

四.排行榜

玩家榜单,有点数榜和一位榜,以及个人详细分析数据

五.玩家管理

添加、编辑玩家,以及设置音效等。

六.表情包

设置玩家头像,本来想做简单点的,后来在朋友的墙裂要求下,改需求,可以在不同排位下显示不同的表情(自定义素材继续找雀魂→_→)

七.音频包

将玩家音效独立出来统一设置,可以为每个玩家单独设置一套音频

最后附上伸手党福音

Saki_v1.2.0_build220429 安装包下载地址:
链接: https://pan.baidu.com/s/14UxLP8kCoXc17SbKyx7Ajw 提取码: ydc3
源代码下载地址:
https://github.com/WaSuper/Mahjong
素材下载地址:
链接: https://pan.baidu.com/s/1qjlaVULnkxzDgXouUMc-tg 提取码: pagu

日本麻将记点器APP相关推荐

  1. 花33元租号玩2小时王者荣耀,未成年为绕过防沉迷用上黑科技上号器App

    梦晨 发自 凹非寺 量子位 报道 | 公众号 QbitAI 史上最严防沉迷规定的第一周,已经有小学生租号玩王者荣耀了. 据央视新闻曝光,租一个等级高.皮肤多的账号玩2小时就要花费33元. 而且不是店家 ...

  2. 五分钟没有操作自动退出_智阅云自动阅读器app下载-智阅云自动阅读器app安卓版下载v1.0...

    智阅云自动阅读器是一款可以阅读赚钱的软件,这里有很多的文章,每天也都会有更新,在这里阅读文章赚钱还是比较靠谱的,大家可以放心. 智阅云自动阅读器app介绍 智阅云自动阅读器这里赚钱是比较简单的,还可以 ...

  3. android 取色器_音乐剪辑器手机版下载-音乐剪辑器app下载v9.10.15 安卓免费版

    音乐剪辑器app是非常棒的剪辑软件,大家可以使用它对各种彩铃铃声,原版音乐进行剪辑编辑处理,功能强大完全免费,而且操作非常简单,有需要的朋友赶快下载试试吧! 音乐剪辑器手机版介绍 有时候想把多首歌曲合 ...

  4. 超级管理器Android,超级文件管理器app

    详情 超级文件管理器app,一款帮助你管理手机内存的软件,还可以重命名,移动,删除文件,在Android平台上完美运行,并能够与其他OI系列软件结合使用! 能快速地查找到目标文件,十分方便用户管理自己 ...

  5. 安卓手机小说阅读器_乐小说阅读器下载手机版-乐小说阅读器app下载安装

    乐小说阅读器app,一款非常不错的小说阅读器,这里的小说资源不仅非常的丰富,而且用户们可以免费任意阅读.小说内容非常的优质,阅读界面非常的清爽简洁,无任何广告弹出.喜欢看小说的就下载试试吧! 关于乐小 ...

  6. Python flask 特殊装饰器 @app.before_request 和 @app.after_request 以及@app.errorhandler介绍

    特殊装饰器 @app.before_request 和 @app.after_request以及@app.errorhandler() 一.背景: Flask我们已经学习很多基础知识了,现在有一个问题 ...

  7. android 语音阅读软件,语音阅读器APP

    语音阅读器APP是一款非常好用的文字转语音阅读软件,不论身在哪里,不论方不方便看文本,该软件都可以帮你转换成语音读给你听,支持文章.小说等等内容的语音,非常方便,需要语音阅读器的可以来西西下载使用! ...

  8. 设计灵感|音乐播放器App界面如何设计?

    音乐播放器 App 界面要怎么设计?来看看集设网精选的 12 款移动端音乐播放器,学习一下如何设计出一个易用性和交互性良好.设计感受舒适.展示层级清晰的界面. 音乐播放器App界面如何设计? - 集设 ...

  9. 音乐播放器App界面优秀案例,通过案例看大咖如何设计?

    音乐播放器 App 界面要怎么设计?集设网 www.ijishe.ccom精选的 12 款移动端音乐播放器,学习一下如何设计出一个易用性和交互性良好.设计感受舒适.展示层级清晰的界面. 看这里

最新文章

  1. 我的理解:什么是WCF
  2. 【攻防世界012】gametime
  3. 【深度学习】常见优化器的PyTorch实现
  4. matlab绘制csv图片,Matlab处理csv文件与图形绘制总结
  5. 每个人都有一条属于自己的路,既然来了这个世间,就好好的修炼好就行了!无须担忧...
  6. Iframe的那些事
  7. Mysql+Navicat for Mysql
  8. LeetCode 404. Sum of Left Leaves
  9. curl 模拟表单提交
  10. 5元以下纯铜小摆件_【拍4斤发5斤】早餐饼干网红早餐代餐曲奇酥性小饼干零食500g6元优惠券券后价5.8元...
  11. Mac 下开启系统自带webserver
  12. mysql中查看密码有效期_Mysql5.7.9密码已过有效期的处理过程
  13. java设置铃声_我的Android进阶之旅------gt;Android系统设置默认来电铃声、闹钟铃声、通知铃声...
  14. ajax上传图片到又拍云,又拍云存储(UpYun)的.NET Core填坑
  15. php session fixation,Session Fixation 原理与防御
  16. 电容降压主要是用在直流稳压电源电路里
  17. 为什么反向传播更加高效
  18. hawk -- pacemaker web GUI
  19. 教资综合素质 (笔记一)
  20. prometheus启动报错Unit prometheus.service entered failed state.

热门文章

  1. 网络摄像机产品介绍以及学习
  2. MyEclipse2017使用maven搭建SSM项目
  3. DIV + CSS 学习笔记(盒模型)
  4. raid中条带概念问题
  5. LPC2124单片机的基础操作——GPIO、外部中断、定时器和串口
  6. 从苏宁电器到卡巴斯基第34篇:我与卡巴斯基的邂逅(上)
  7. 计算机网络笔记17 分类的IP地址
  8. 海康威视旗下萤石网络上市:首日跌12% 公司市值142亿
  9. 锁存器(latch)、触发器(flip-flop)、寄存器(register)的区别
  10. 前端encodeURI , decodeURI对字符串编码解码 , 以及把url中的%0A %20解码成换行和空格