一、说明

  如果想设计一套比较智能的人机麻将程序,那么一定需要能判断手牌好坏的方法,然后及时做出不同的打牌策略。而好坏最重要的指标就是入听步数,也就是达到听牌前的最小换牌次数。
  实现方式为oracle的存储过程,规则采用的是通用麻将游戏,胡牌公式为:x123+y111+11(其中123表示顺子,111表示坎子,11表示对子,x和y表示个数),为方便代码处理,用数字来代表麻将牌,如下:
  1~9 表示 一到九万
  11~19 表示 一饼到九饼
  21~29表示 一条到九条
  31 中 32 发 33白

二、核心思路

  首先过滤已经成型的子牌(顺子和坎子);然后标记出卡号、连号、对子和单张;最后用单张搭配卡号或连号凑成顺子,或用单张搭配对子凑成坎子,并记录匹配次数即是入听步数。其中卡号表示两个号码中间间隔一个号,如2和4、7和9
  如果搭配完后还剩单张,则全部配成对子和坎子,可能存在的张数以及对应的步数结果如下:

单张数

入听步数

14

9

11

7

8

5

5

3

2

1

*若用x表示单张个数,则步数公式为:trunc(x/3)2+1
  如果剩的是卡号、连号或对子,则采用拆一个配另外两个的方式。如1和2、5和7、11和11,拆掉第一个,换成6和11,就能凑成一个顺子和坎子。可能存在的张数以及对应的步数结果如下:

卡号、连号或对子总张数

入听步数

14

5

8

3

2

1

*若用x表示单张个数,则步数公式为:trunc(x/6)2+1
  上面操作得到的步数总和再减1就是入听步数

三、具体代码以及测试结果

  建表并插入测试数据:

--创建临时表
CREATE TABLE TEMP
(
  chr1 VARCHAR2(100),    --标记顺子,坎子,对子
  chr2 VARCHAR2(100),    --标记入听步数
  chr3 VARCHAR2(100),    --用S标记连号和卡号,用D标记对子
  num1 NUMBER            --麻将id
);
--插入测试数据,14张牌
INSERT INTO TEMP (num1) VALUES (1);
INSERT INTO TEMP (num1) VALUES (4);
INSERT INTO TEMP (num1) VALUES (6);
INSERT INTO TEMP (num1) VALUES (7);
INSERT INTO TEMP (num1) VALUES (7);
INSERT INTO TEMP (num1) VALUES (11);
INSERT INTO TEMP (num1) VALUES (13);
INSERT INTO TEMP (num1) VALUES (14);
INSERT INTO TEMP (num1) VALUES (21);
INSERT INTO TEMP (num1) VALUES (21);
INSERT INTO TEMP (num1) VALUES (22);
INSERT INTO TEMP (num1) VALUES (22);
INSERT INTO TEMP (num1) VALUES (23);
INSERT INTO TEMP (num1) VALUES (24);

  存储过程实现方法:

--设置输出
SET SERVEROUTPUT ON

--计算入听步数
DECLARE
  an_result NUMBER;
  ln_num NUMBER;
  ln_tmp NUMBER;
  i NUMBER:=0;
  ln_D NUMBER;
  ln_S NUMBER;
BEGIN

  an_result := 0;

  --标记出顺子,坎子
  WHILE 1=1 LOOP

    --查出数值最小的牌,作为顺子的首张
    SELECT MIN(num1) INTO ln_num FROM temp WHERE chr1 IS NULL;
    SELECT COUNT(1) INTO ln_tmp FROM temp WHERE chr1 IS NULL AND num1 = ln_num;
    
    IF ln_tmp > 0 AND ln_tmp <= 2 THEN
      UPDATE temp SET chr1 = 'S'||ln_num WHERE chr1 IS NULL AND num1 = ln_num AND rownum <= 1;
      UPDATE temp SET chr1 = 'S'||ln_num WHERE chr1 IS NULL AND num1 = ln_num+1 AND rownum <= 1;
      ln_tmp := SQL%ROWCOUNT ;
      IF ln_tmp = 0 THEN CONTINUE; END IF;
      UPDATE temp SET chr1 = 'S'||ln_num WHERE chr1 IS NULL AND num1 = ln_num+2 AND rownum <= 1;
      ln_tmp := SQL%ROWCOUNT ;
      IF ln_tmp = 0 THEN CONTINUE; END IF;
    ELSIF ln_tmp > 2 THEN
      UPDATE temp SET chr1 = 'K'||num1,  chr2 = 0 WHERE chr1 IS NULL AND num1 = ln_num AND rownum <= 3;
    ELSE
      EXIT;
    END IF;

    --已成型的过滤
    UPDATE temp SET chr2 = 0 WHERE chr1 = 'S'||ln_num;
    
  END LOOP;

  --剩余的置空,以便重新标记
  UPDATE temp SET chr1 = NULL WHERE chr2 IS NULL;

  --标记卡号,连号
  WHILE 1=1 LOOP

    --查出数值最小的牌,作为顺子的首张
    SELECT MIN(num1) INTO ln_num FROM temp WHERE chr1 IS NULL;
    SELECT COUNT(1) INTO ln_tmp FROM temp WHERE chr1 IS NULL AND num1 = ln_num;
    i := i+1;
    
    IF ln_tmp = 1 THEN
      UPDATE temp SET chr1 = 's'||i WHERE chr1 IS NULL AND num1 = ln_num AND rownum <= 1;
      UPDATE temp SET chr1 = 's'||i WHERE chr1 IS NULL AND num1 = ln_num+1 AND rownum <= 1;
      ln_tmp := SQL%ROWCOUNT ;
      IF ln_tmp = 0 THEN 
        UPDATE temp SET chr1 = 's'||i WHERE chr1 IS NULL AND num1 = ln_num+2 AND rownum <= 1;
        ln_tmp := SQL%ROWCOUNT ;
        IF ln_tmp = 0 THEN CONTINUE; END IF;
      END IF;
    ELSIF ln_tmp = 2 THEN
      UPDATE temp SET chr1 = 'D'||num1, chr3 = 'D' WHERE chr1 IS NULL AND num1 = ln_num;
    ELSE
      EXIT;
    END IF;

  END LOOP;
  
  --标记连号和卡号
  UPDATE temp
     SET chr3 = 'S'
   WHERE chr2 IS NULL
     AND chr1 IN (SELECT chr1 FROM temp WHERE chr2 IS NULL AND chr3 IS NULL GROUP BY chr1 HAVING COUNT(1) = 2);

  --单张配连号,卡号和对子,对子留最后
  FOR cur IN (SELECT DISTINCT chr1, chr3 FROM temp  WHERE chr3 IN ('S', 'D') ORDER BY chr3 DESC) LOOP

    UPDATE temp SET chr2 = an_result+1 WHERE chr2 IS NULL AND chr3 IS NULL AND rownum <=1;
    ln_tmp := SQL%ROWCOUNT;
    IF ln_tmp = 0 THEN EXIT; END IF;
    UPDATE temp SET chr2 = an_result+1 WHERE chr1 = cur.chr1;
    an_result := an_result+1;

  END LOOP;
  
  SELECT COUNT(1) INTO ln_tmp FROM temp WHERE chr2 IS NULL AND chr3 IS NULL;
  
  IF ln_tmp > 0 THEN
    an_result := an_result+trunc(ln_tmp/3)*2;
    DBMS_OUTPUT.PUT_LINE(an_result);
    --有单张,说明无连号,卡号和对子,直接结束
    RETURN;
  END IF;
  
  SELECT COUNT(1)/2 INTO ln_D FROM temp WHERE chr3 = 'D' AND chr2 IS NULL;
  SELECT COUNT(1) INTO ln_S FROM temp WHERE chr3 = 'S' AND chr2 IS NULL;
  
  IF ln_D <= 1 AND ln_S = 0 THEN
    an_result := an_result-1;
  ELSE
    an_result := an_result+trunc(ln_tmp/6)*2;
  END IF;

  DBMS_OUTPUT.PUT_LINE(an_result);
  COMMIT;

END;
/

  执行结果:

java判定麻将胡清一色 如何判断程序麻将_SQL