本项目实现了一个简易的日麻役种计算器,技术栈为Java Swing。包括前端的选牌,看你选的牌,根据你选的牌计算等部分。
主页面部分:
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Objects;
import java.util.Map;
public class MainJ extends JFrame {
private Graphics g;
private JLabel pointsLabel;
private static final String IMAGE_DIR = "images/"; // 图片存放目录
private static final String[] TILE_NAMES = {
// 万子
"wan1", "wan2", "wan3", "wan4", "wan5", "wan5bao", "wan6", "wan7", "wan8", "wan9",
// 筒子
"tong1", "tong2", "tong3", "tong4", "tong5", "tong5bao", "tong6", "tong7", "tong8", "tong9",
// 索子
"suo1", "suo2", "suo3", "suo4", "suo5", "suo5bao", "suo6", "suo7", "suo8", "suo9",
// 字牌和特殊牌
"special_dong", "special_nan", "special_xi", "special_north",
"special_zhong", "special_fa", "special_bai"
};
private MahjongTile[] mainList = new MahjongTile[14];
private int mainListIndex = 0;
private int finalScore = 0;
private static final Map<String, MahjongTile> TILE_MAP = new HashMap<>();
static {
TILE_MAP.put("wan1", MahjongTile.WAN_1);
TILE_MAP.put("wan2", MahjongTile.WAN_2);
TILE_MAP.put("wan3", MahjongTile.WAN_3);
TILE_MAP.put("wan4", MahjongTile.WAN_4);
TILE_MAP.put("wan5", MahjongTile.WAN_5);
TILE_MAP.put("wan5bao", MahjongTile.WAN_5BAO);
TILE_MAP.put("wan6", MahjongTile.WAN_6);
TILE_MAP.put("wan7", MahjongTile.WAN_7);
TILE_MAP.put("wan8", MahjongTile.WAN_8);
TILE_MAP.put("wan9", MahjongTile.WAN_9);
// 筒子
TILE_MAP.put("tong1", MahjongTile.TONG_1);
TILE_MAP.put("tong2", MahjongTile.TONG_2);
TILE_MAP.put("tong3", MahjongTile.TONG_3);
TILE_MAP.put("tong4", MahjongTile.TONG_4);
TILE_MAP.put("tong5", MahjongTile.TONG_5);
TILE_MAP.put("tong5bao", MahjongTile.TONG_5BAO);
TILE_MAP.put("tong6", MahjongTile.TONG_6);
TILE_MAP.put("tong7", MahjongTile.TONG_7);
TILE_MAP.put("tong8", MahjongTile.TONG_8);
TILE_MAP.put("tong9", MahjongTile.TONG_9);
// 索子
TILE_MAP.put("suo1", MahjongTile.SUO_1);
TILE_MAP.put("suo2", MahjongTile.SUO_2);
TILE_MAP.put("suo3", MahjongTile.SUO_3);
TILE_MAP.put("suo4", MahjongTile.SUO_4);
TILE_MAP.put("suo5", MahjongTile.SUO_5);
TILE_MAP.put("suo5bao", MahjongTile.SUO_5BAO);
TILE_MAP.put("suo6", MahjongTile.SUO_6);
TILE_MAP.put("suo7", MahjongTile.SUO_7);
TILE_MAP.put("suo8", MahjongTile.SUO_8);
TILE_MAP.put("suo9", MahjongTile.SUO_9);
// 字牌和特殊牌
TILE_MAP.put("special_dong", MahjongTile.DONG);
TILE_MAP.put("special_nan", MahjongTile.NAN);
TILE_MAP.put("special_xi", MahjongTile.XI);
TILE_MAP.put("special_north", MahjongTile.BEI);
TILE_MAP.put("special_zhong", MahjongTile.ZHONG);
TILE_MAP.put("special_fa", MahjongTile.FA);
TILE_MAP.put("special_bai", MahjongTile.BAI);
}
public MainJ() {
JPanel backgroundPanel = new JPanel(new BorderLayout()) {
private Image backgroundImage;
{
try {
// 从文件或资源路径加载图片
backgroundImage = ImageIO.read(getClass().getResource(IMAGE_DIR+"face.png"));
} catch (IOException e) {
e.printStackTrace();
JOptionPane.showMessageDialog(this, "无法加载背景图片!");
}
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (backgroundImage != null) {
// 绘制图片(自适应面板大小)
g.drawImage(backgroundImage, 0, 0, getWidth(), getHeight(), this);
}
}
};
setTitle("役种/番数计算器");
setExtendedState(JFrame.MAXIMIZED_BOTH);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
GridLayout layout = new GridLayout(4, 10);
JPanel cards = new JPanel(layout);
cards.setBackground(Color.gray);
JPanel alreadies = new JPanel(new GridLayout(1,14));
alreadies.setPreferredSize(new Dimension(getWidth(), 160));
alreadies.setBackground(Color.ORANGE);
cards.setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
pointsLabel = new JLabel("最终点数:0");
pointsLabel.setFont(new Font("仿宋", Font.BOLD, 55));
pointsLabel.setForeground(Color.MAGENTA);
pointsLabel.setBounds(5, 2, 200, 300);
pointsLabel.setVisible(true);
gbc.fill = GridBagConstraints.BOTH;
gbc.insets = new Insets(5, 2, 5, 2); // 组件之间的间距
int index = 0;
for (String tileName : TILE_NAMES) {
ImageIcon imgIcon = new ImageIcon(Objects.requireNonNull(getClass().getResource(IMAGE_DIR + tileName + ".png")));
if (imgIcon.getImageLoadStatus() == MediaTracker.COMPLETE) {
JLabel label = new JLabel(imgIcon);
label.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
MahjongTile tile = TILE_MAP.get(tileName);
if (tile != null && mainListIndex < mainList.length) {
ImageIcon tileIcon = new ImageIcon(getClass().getResource(IMAGE_DIR + tileName + ".png"));
JLabel selectedLabel = new JLabel(tileIcon);
// 添加到已选牌区域
alreadies.add(selectedLabel);
alreadies.revalidate(); // 刷新布局
alreadies.repaint();
// 更新数据存储
mainList[mainListIndex++] = tile;
System.out.println("已选牌: " + tile);
if (mainListIndex == mainList.length+1) {
boolean isHule = true; // 假设已经和牌
MahBooleans mahBooleans = new MahBooleans(); // 根据实际情况设置参数
BackendCalc bc = new BackendCalc(mainList, isHule, mahBooleans);
finalScore = bc.getFinalScore();
pointsLabel.setText("最终点数:" + finalScore); // 更新点数显示
pointsLabel.setVisible(true);
}
}
}
});
gbc.gridx = index % 10; // 设置x坐标
gbc.gridy = index / 10; // 设置y坐标
cards.add(label, gbc);
index++;
} else {
System.err.println("Failed to load image: " + tileName);
}
}
backgroundPanel.add(cards, BorderLayout.SOUTH);
backgroundPanel.add(alreadies,BorderLayout.NORTH);
add(backgroundPanel);
backgroundPanel.add(pointsLabel);
MahjongListener mahjongListener = new MahjongListener();
cards.addMouseListener(mahjongListener);
setContentPane(backgroundPanel);
setVisible(true);
System.out.println(mainList);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new MainJ());
}
}
可以看到,里面主要有诸如主页面牌的位置,54张牌的可供选择的点击地点,和已经被选择的牌的显示(当然还有一个答案显示和背景Panel)
enum牌堆:
public enum MahjongTile {
// 万(Wan)
WAN_1(Suit.WAN, 1, false, 0),
WAN_2(Suit.WAN, 2, false, 1),
WAN_3(Suit.WAN, 3, false, 2),
WAN_4(Suit.WAN, 4, false, 3),
WAN_5(Suit.WAN, 5, false, 4),
WAN_5BAO(Suit.WAN, 5, true, 5),
WAN_6(Suit.WAN, 6, false, 6),
WAN_7(Suit.WAN, 7, false, 7),
WAN_8(Suit.WAN, 8, false, 8),
WAN_9(Suit.WAN, 9, false, 9),
// 筒(Tong)
TONG_1(Suit.TONG, 1, false, 10),
TONG_2(Suit.TONG, 2, false, 11),
TONG_3(Suit.TONG, 3, false, 12),
TONG_4(Suit.TONG, 4, false, 13),
TONG_5(Suit.TONG, 5, false, 14),
TONG_5BAO(Suit.TONG, 5, true, 15),
TONG_6(Suit.TONG, 6, false, 16),
TONG_7(Suit.TONG, 7, false, 17),
TONG_8(Suit.TONG, 8, false, 18),
TONG_9(Suit.TONG, 9, false, 19),
// 索(Suo)
SUO_1(Suit.SUO, 1, false, 20),
SUO_2(Suit.SUO, 2, false, 21),
SUO_3(Suit.SUO, 3, false, 22),
SUO_4(Suit.SUO, 4, false, 23),
SUO_5(Suit.SUO, 5, false, 24),
SUO_5BAO(Suit.SUO, 5, true, 25),
SUO_6(Suit.SUO, 6, false, 26),
SUO_7(Suit.SUO, 7, false, 27),
SUO_8(Suit.SUO, 8, false, 28),
SUO_9(Suit.SUO, 9, false, 29),
// 字牌(Zi)
DONG(Suit.ZI, 0, false, 30), // 东南西北中发白
NAN(Suit.ZI, 0, false, 31),
XI(Suit.ZI, 0, false, 32),
BEI(Suit.ZI, 0, false, 33),
ZHONG(Suit.ZI, 0, false, 34),
FA(Suit.ZI, 0, false, 35),
BAI(Suit.ZI, 0, false, 36);
public enum Suit {
WAN, TONG, SUO, ZI
}
private final Suit suit;
private final int number;
private final boolean isBao;
private final int order; // 用于排序的字段
MahjongTile(Suit suit, int number, boolean isBao, int order) {
this.suit = suit;
this.number = number;
this.isBao = isBao;
this.order = order;
}
public Suit getSuit() {
return suit;
}
public int getNumber() {
return number;
}
public boolean isBao() {
return isBao;
}
public int getOrder() {
return order;
}
}
包括是否是宝牌,牌面数字,牌种类,是牌堆中第几张等等。
计算器核心逻辑:
import java.util.Arrays;
import java.util.Comparator;
public class BackendCalc {
MahjongTile[] user;
MahBooleans boolor = new MahBooleans();
private int finalScore;
private void sortMahjongs(MahjongTile[] user) {
Arrays.sort(user, Comparator.comparingInt(MahjongTile::getOrder));
}
public int getFinalScore() {
return finalScore;
}
private boolean isTripleS(MahjongTile a, MahjongTile b, MahjongTile c){
if(a.getSuit() != b.getSuit() || b.getSuit() != c.getSuit()){
return false;
}
if(a == b &&b == c){
return true;
} else if (b.getNumber() == a.getNumber()+1&&c.getNumber() == b.getNumber()+1) {
return true;
}
return false;
}
private boolean isHu(MahjongTile[] user) {
int triple_ke_shun = 0, double_jiang_quetou = 0,flag_search = 1;
MahjongTile temp1 = null,temp2 = null;
for(MahjongTile i:user){
switch (flag_search){
case 1:
temp1 = i;
flag_search = 2;
break;
case 2:
temp2 = i;
flag_search = 3;
break;
case 3:
if(isTripleS(temp1,temp2,i)){
triple_ke_shun++;
}else if(temp1 == temp2){
double_jiang_quetou ++;
}
flag_search = 1;
}
}
if(triple_ke_shun == 4&&double_jiang_quetou == 1){
return true;
}
return false;
}
private int pointsCalc(MahBooleans mahBooleans) {
// ==== 基础参数 ====
int han = calculateHan(mahBooleans); // 计算总番数
int fu = calculateFu(mahBooleans); // 计算符数
boolean isDealer = mahBooleans.getDealer();
boolean isTsumo = mahBooleans.getTsumo();
// ==== 基础点数计算 ====
int basePoints = calculateBasePoints(han, fu);
// ==== 最终点数计算 ====
int finalScore = calculateFinalScore(basePoints, isDealer, isTsumo);
// 向上取整到整百
return (int) Math.ceil(finalScore / 100.0) * 100;
}
// 计算总番数
private int calculateHan(MahBooleans mb) {
int han = 0;
// 役种计算(示例部分)
if(mb.getTanyao()) han += 1; // 断幺九
if(mb.getRiichi()) han += 1; // 立直
if(mb.getIppatsu()) han += 1; // 一发
if(mb.getMenzen() && mb.getTsumo()) han += 1; // 门清自摸
// ...其他役种判断...
return han;
}
// 符数计算
private int calculateFu(MahBooleans mb) {
// 特殊牌型判断
// if(isChiitoitsu(mb)) return 25; // 七对子
// if(isPinfu(mb)) return mb.getTsumo() ? 20 : 30; // 平和
//
int fu = 20; // 基础符
//
// // 雀头加符
// if(isYakuPair(mb)) fu += 2; // 役牌雀头
//
// // 刻杠加符
// fu += calculateKotsuFu(mb); // 刻子计算
//
// // 和牌形式
// if(isSingleWait(mb)) fu += 2; // 单骑听牌
// if(isEdgeOrClosedWait(mb)) fu += 2; // 边张/嵌张
if(mb.getTsumo()) fu += 2; // 自摸
if(mb.getMenzen() && mb.getRon()) fu += 10; // 门清荣和
// 向上取整到十位
return (int) Math.ceil(fu / 10.0) * 10;
}
// 基础点数计算
private int calculateBasePoints(int han, int fu) {
if(han >= 13) return 8000; // 役满
if(han >= 11) return 6000; // 三倍满
if(han >= 8) return 4000; // 倍满
if(han >= 6) return 3000; // 跳满
if(han >= 5) return 2000; // 满贯
// 精确计算
int base = fu * (int) Math.pow(2, han + 2);
return Math.min(base, 2000); // 满贯封顶
}
// 最终点数分配
private int calculateFinalScore(int base, boolean isDealer, boolean isTsumo) {
if(isDealer) {
return isTsumo ?
base * 2 * 3 : // 自摸每家付2a
base * 6; // 荣和付6a
} else {
return isTsumo ?
(base * 2) + (base * 1 * 2) : // 庄家2a 闲家1a
base * 4; // 荣和付4a
}
}
// // ==== 辅助方法 ====
// private boolean isChiitoitsu(MahBooleans mb) {
// // 判断七对子逻辑
// return mb.getIipeiko() && ...;
// }
//
// private boolean isPinfu(MahBooleans mb) {
// // 判断平和逻辑
// return !mb.getTanyao() && ...;
// }
public BackendCalc(MahjongTile[] user,boolean isHubool,MahBooleans boolor) {
this.user = user;
this.boolor = boolor;
sortMahjongs(user);
if(isHu(user)){
isHubool = true;
finalScore = pointsCalc(boolor);
}else {
isHubool = false;
}
}
}
(本人并不懂全部的规则,所以计算部分的完善只能后人补充了)
各种各样的“是与非”
public class MahBooleans {
private Boolean isDealer = false;
/** 是否为自摸和牌(与荣和互斥) */
private Boolean isTsumo = false;
/** 是否为荣和(他人放铳) */
private Boolean isRon = false;
/** 是否为门清状态(无吃、碰、明杠) */
private Boolean isMenzen = false;
// ---------- 特殊和牌方式 ----------
/** 是否立直(宣言立直) */
private Boolean isRiichi = false;
/** 是否一发(立直后一轮内和牌) */
private Boolean isIppatsu = false;
/** 是否海底摸月(自摸牌山最后一张) */
private Boolean isHaitei = false;
/** 是否河底捞鱼(荣和最后一张舍牌) */
private Boolean isHoutei = false;
/** 是否岭上开花(开杠后摸牌和牌) */
private Boolean isRinshan = false;
/** 是否枪杠(荣和对手加杠的牌) */
private Boolean isChankan = false;
// ---------- 役种与规则 ----------
/** 是否断幺九(无幺九牌) */
private Boolean isTanyao = false;
/** 是否一杯口(两组相同顺子,门清限定) */
private Boolean isIipeiko = false;
/** 是否三色同顺(三组同数字不同色顺子) */
private Boolean isSanshoku = false;
/** 是否一气通贯(同一色1-9顺子) */
private Boolean isIttsu = false;
/** 是否混全带幺九(所有面子含幺九,副露可) */
private Boolean isChanta = false;
/** 是否清一色(全手牌单一色) */
private Boolean isChinitsu = false;
/** 是否役满(如天和、国士无双等) */
private Boolean isYakuman = false;
/** 是否累计役满(多个役种累计达到役满) */
private Boolean isMultipleYakuman = false;
// ---------- 特殊场况 ----------
/** 是否流局满贯(流局时未鸣牌且手牌全为幺九字牌) */
private Boolean isNagashi = false;
/** 是否为天和(庄家起手和牌) */
private Boolean isTenhou = false;
/** 是否为地和(闲家第一巡和牌) */
private Boolean isChiihou = false;
// ---------- 宝牌相关 ----------
/** 是否包含宝牌(需额外输入数量) */
private Boolean hasDora = false;
/** 是否包含里宝牌(立直后生效) */
private Boolean hasUraDora = false;
/** 是否包含赤宝牌(红五万/筒/索) */
private Boolean hasRedDora = false;
public Boolean getHasDora() {
return hasDora;
}
public void setHasDora(Boolean hasDora) {
this.hasDora = hasDora;
}
public Boolean getHasRedDora() {
return hasRedDora;
}
public void setHasRedDora(Boolean hasRedDora) {
this.hasRedDora = hasRedDora;
}
public Boolean getHasUraDora() {
return hasUraDora;
}
public void setHasUraDora(Boolean hasUraDora) {
this.hasUraDora = hasUraDora;
}
public Boolean getChankan() {
return isChankan;
}
public void setChankan(Boolean chankan) {
isChankan = chankan;
}
public Boolean getChanta() {
return isChanta;
}
public void setChanta(Boolean chanta) {
isChanta = chanta;
}
public Boolean getChiihou() {
return isChiihou;
}
public void setChiihou(Boolean chiihou) {
isChiihou = chiihou;
}
public Boolean getChinitsu() {
return isChinitsu;
}
public void setChinitsu(Boolean chinitsu) {
isChinitsu = chinitsu;
}
public Boolean getDealer() {
return isDealer;
}
public void setDealer(Boolean dealer) {
isDealer = dealer;
}
public Boolean getHaitei() {
return isHaitei;
}
public void setHaitei(Boolean haitei) {
isHaitei = haitei;
}
public Boolean getHoutei() {
return isHoutei;
}
public void setHoutei(Boolean houtei) {
isHoutei = houtei;
}
public Boolean getIipeiko() {
return isIipeiko;
}
public void setIipeiko(Boolean iipeiko) {
isIipeiko = iipeiko;
}
public Boolean getIppatsu() {
return isIppatsu;
}
public void setIppatsu(Boolean ippatsu) {
isIppatsu = ippatsu;
}
public Boolean getIttsu() {
return isIttsu;
}
public void setIttsu(Boolean ittsu) {
isIttsu = ittsu;
}
public Boolean getMenzen() {
return isMenzen;
}
public void setMenzen(Boolean menzen) {
isMenzen = menzen;
}
public Boolean getMultipleYakuman() {
return isMultipleYakuman;
}
public void setMultipleYakuman(Boolean multipleYakuman) {
isMultipleYakuman = multipleYakuman;
}
public Boolean getNagashi() {
return isNagashi;
}
public void setNagashi(Boolean nagashi) {
isNagashi = nagashi;
}
public Boolean getRiichi() {
return isRiichi;
}
public void setRiichi(Boolean riichi) {
isRiichi = riichi;
}
public Boolean getRinshan() {
return isRinshan;
}
public void setRinshan(Boolean rinshan) {
isRinshan = rinshan;
}
public Boolean getRon() {
return isRon;
}
public void setRon(Boolean ron) {
isRon = ron;
}
public Boolean getSanshoku() {
return isSanshoku;
}
public void setSanshoku(Boolean sanshoku) {
isSanshoku = sanshoku;
}
public Boolean getTanyao() {
return isTanyao;
}
public void setTanyao(Boolean tanyao) {
isTanyao = tanyao;
}
public Boolean getTenhou() {
return isTenhou;
}
public void setTenhou(Boolean tenhou) {
isTenhou = tenhou;
}
public Boolean getTsumo() {
return isTsumo;
}
public void setTsumo(Boolean tsumo) {
isTsumo = tsumo;
}
public Boolean getYakuman() {
return isYakuman;
}
public void setYakuman(Boolean yakuman) {
isYakuman = yakuman;
}
}
通过这些内容,我们可以实现日麻役种的简易计算。
1万+

被折叠的 条评论
为什么被折叠?



