一、说明
如果想设计一套比较智能的人机麻将程序,那么一定需要能判断手牌好坏的方法,然后及时做出不同的打牌策略。而好坏最重要的指标就是入听步数,也就是达到听牌前的最小换牌次数。
实现方式为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;
/
执行结果: