package com.github.blovemaple.mj.rule.simple;
import static com.github.blovemaple.mj.object.StandardTileUnitType.*;
import static com.github.blovemaple.mj.utils.MyUtils.*;
import static java.util.Collections.*;
import static java.util.Comparator.*;
import static java.util.stream.Collectors.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Queue;
import java.util.Set;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.stream.Stream;
import com.github.blovemaple.mj.object.PlayerInfo;
import com.github.blovemaple.mj.object.Tile;
import com.github.blovemaple.mj.object.TileGroup;
import com.github.blovemaple.mj.object.TileRank;
import com.github.blovemaple.mj.object.TileRank.NumberRank;
import com.github.blovemaple.mj.rule.win.WinInfo;
import com.github.blovemaple.mj.rule.win.WinType;
import com.github.blovemaple.mj.object.TileSuit;
import com.github.blovemaple.mj.object.TileType;
import com.github.blovemaple.mj.object.TileUnit;
import com.github.blovemaple.mj.object.TileUnitType;
/**
* 普通和牌(相对于七对等特殊和牌类型而言)。
*
* @author blovemaple <blovemaple2010(at)gmail.com>
*/
public class NormalWinType implements WinType {
private static final NormalWinType INSTANCE = new NormalWinType();
public static NormalWinType get() {
return INSTANCE;
}
private NormalWinType() {
}
private static class ParseBranch {
List<TileUnit> units;
boolean hasJiang;
List<Tile> remainTiles;
ParseBranch(List<TileUnit> units, boolean hasJiang, List<Tile> remainTiles) {
this.units = units;
this.hasJiang = hasJiang;
this.remainTiles = remainTiles;
}
ParseBranch(List<Tile> aliveTileList) {
this(emptyList(), false, aliveTileList);
}
}
@Override
public List<List<TileUnit>> parseWinTileUnits(WinInfo winInfo) {
if (winInfo.getUnits() != null) {
List<List<TileUnit>> units = winInfo.getUnits().get(this);
if (units != null)
return units;
}
Tile winTile = winInfo.getWinTile();
Boolean ziMo = winInfo.getZiMo();
if (winInfo.getAliveTiles().size() % 3 != 2) {
winInfo.setUnits(this, Collections.emptyList());
return Collections.emptyList();
}
// 根据牌组生成units
List<TileUnit> groupUnits = genGroupUnits(winInfo.getTileGroups());
// 将手牌按type排序,开始parse
List<Tile> aliveTileList = winInfo.getAliveTiles().stream().sorted(comparing(Tile::type)).collect(toList());
List<List<TileUnit>> parseResults = new LinkedList<>();
Queue<ParseBranch> branchQueue = new LinkedList<>();
branchQueue.add(new ParseBranch(aliveTileList));
ParseBranch crtBranch;
while ((crtBranch = branchQueue.poll()) != null) {
List<Tile> jiangTiles = null, keTiles = null, shunTiles = null;
TileType firstType = null;
TileSuit suit = null;
int lastNumber = -1;
boolean first = true;
for (Tile tile : crtBranch.remainTiles) {
if (first) {
firstType = tile.type();
suit = firstType.suit();
// 将
if (!crtBranch.hasJiang) {
jiangTiles = new ArrayList<>();
jiangTiles.add(tile);
}
// 刻
keTiles = new ArrayList<>();
keTiles.add(tile);
// 顺
TileRank<?> crtRank = tile.type().rank();
if (crtRank instanceof NumberRank && (lastNumber = ((NumberRank) crtRank).number()) <= 7) {
shunTiles = new ArrayList<>();
shunTiles.add(tile);
}
first = false;
} else {
TileType crtType = tile.type();
// 将
if (jiangTiles != null) {
if (crtType == firstType) {
jiangTiles.add(tile);
newBranchOrResult(crtBranch, jiangTiles, JIANG, branchQueue, parseResults, groupUnits,
winTile, ziMo);
jiangTiles = null;
} else
jiangTiles = null;
}
// 刻
if (keTiles != null) {
if (crtType == firstType) {
keTiles.add(tile);
if (keTiles.size() == 3) {
newBranchOrResult(crtBranch, keTiles, KEZI, branchQueue, parseResults, groupUnits,
winTile, ziMo);
keTiles = null;
}
} else
keTiles = null;
}
// 顺
if (shunTiles != null) {
TileSuit crtSuit = crtType.suit();
if (crtSuit == suit) {
int crtNumber = ((NumberRank) crtType.rank()).number();
switch (crtNumber - lastNumber) {
case 1:
lastNumber++;
shunTiles.add(tile);
if (shunTiles.size() == 3) {
newBranchOrResult(crtBranch, shunTiles, SHUNZI, branchQueue, parseResults,
groupUnits, winTile, ziMo);
shunTiles = null;
}
break;
case 0:
break;
default:
shunTiles = null;
break;
}
} else
shunTiles = null;
}
}
if (jiangTiles == null && keTiles == null && shunTiles == null)
break;
}
}
winInfo.setUnits(this, parseResults);
return parseResults;
}
/**
* 从玩家的所有牌组生成单元集合。
*/
protected List<TileUnit> genGroupUnits(List<TileGroup> tileGroups) {
return tileGroups.stream()
.map(group -> TileUnit.got(group.getType().getUnitType(), group.getTiles(), group.getGotTile()))
.collect(toList());
}
private void newBranchOrResult(ParseBranch baseBranch, List<Tile> selectedTiles, TileUnitType newUnitType,
Queue<ParseBranch> branchQueue, List<List<TileUnit>> parseResults, List<TileUnit> groupUnits, Tile winTile,
Boolean ziMo) {
boolean hasJiang = baseBranch.hasJiang || newUnitType == JIANG;
TileUnit newUnit = winTile != null && selectedTiles.contains(winTile) && (ziMo != null && !ziMo)
? TileUnit.got(newUnitType, selectedTiles, winTile) : TileUnit.self(newUnitType, selectedTiles);
List<TileUnit> units = merged(ArrayList::new, baseBranch.units, newUnit);
if (baseBranch.remainTiles.size() != selectedTiles.size()) {
// 牌没选完,生成一个分支
List<Tile> remainTiles = remainColl(ArrayList<Tile>::new, baseBranch.remainTiles, selectedTiles);
branchQueue.add(new ParseBranch(units, hasJiang, remainTiles));
} else {
// 牌都选完了,生成一个结果
if (hasJiang)
parseResults.add(merged(ArrayList::new, groupUnits, units));
}
}
@Override
public List<Tile> getDiscardCandidates(Set<Tile> aliveTiles, Collection<Tile> candidates) {
// 先保证顺子刻子的定义都是3张牌、将牌是2张牌
if (SHUNZI.size() != 3 || KEZI.size() != 3)
throw new RuntimeException();
if (JIANG.size() != 2)
throw new RuntimeException();
if (aliveTiles.isEmpty())
return emptyList();
List<Tile> aliveTileList = new ArrayList<>(aliveTiles);
// 解析出全部的preShunke
Map<TileSuit, List<Tile>> candidatesBySuit = candidates.stream()
.collect(groupingBy(tile -> tile.type().suit()));
List<PreUnit> preShunkes = parsePreShunkes(aliveTileList, candidatesBySuit);
// 按照从好到差排序(牌数越多越好,牌数相同的,lackedTypes种类越多越好)
preShunkes.sort(Comparator.<PreUnit, Integer> comparing(preUnit -> preUnit.tiles.size())
.thenComparing(preUnit -> preUnit.lackedTypesList.size()).reversed());
// 找出与最差preShunke情况相同的units中的牌
Set<Tile> remainTiles = new HashSet<>(aliveTiles);
List<Tile> tilesFromWorstUnits = new ArrayList<>();
@SuppressWarnings("unused")
int crtUnitSize = Integer.MAX_VALUE, crtLackedKinds = Integer.MAX_VALUE;
for (PreUnit shunke : preShunkes) {
if (remainTiles.isEmpty())