8

I am able to calculate the shanten number myself, but it often takes me more than a minute. I'm looking for ideas how to become more efficient.

Background: I'm currently writing a paper about determining the shanten number with a computer algorithm. This works very well so far, but there are likely some ideas for improvements that I missed. I am a really poor Mahjong player myself, so input by experienced players would be most welcome.

This is a rough description of my (manual) strategy right now:

First, detect any tiles of which the usage is certain. This means tiles that are alone (no adjacent tile within a radius of 2) and sets that are completed (chi, pon).

I keep count of how many single tiles there are. Next, I look for incomplete sets (in a shape similar to any of 11x, 12x, 1x3, x23). I insert one of the singles into any of them and increment the shanten number in my head. During this step, I attempt to keep up to 2 pairs for later though.

There will be some singles or incomplete sets be left over, I split them up and multiply their number with 2/3 (because any 3 tiles can be made into a set by exchanging 2 of them). I also check if there are one or two pairs, if not, I create one (increasing shanten by 1). So far no problem at all, this only takes me a few seconds.

The trouble starts when there are multiple options how to combine tiles. Should I split this set up, or should I keep it? Should I add this tile here or there? Especially pure (only one suit) hands are annoyingly complex, and I could not figure out a fast way to solve them.

My algorithm of course just laughs at the hands I have trouble with, as it is able to solve even the most complicated hands in fractions of a second. It explores all reasonable ways how to combine the tiles in a tree (with heuristics) and yields the best option.

  • For clarity, shall we assume you're just looking to complete any hand, rather than caring about the value or shape thereof? – goldPseudo May 2 '13 at 6:28
  • @gold Yes. Yaku etc are not important. – mafu May 2 '13 at 10:39
  • Already answered here: stackoverflow.com/questions/4239028/… – Forget I was ever here Nov 25 '13 at 23:50
  • @PieterGeerkens Yes, I found a way that works somewhat nicely, luckily. I should have linked the questions. I'll leave this on here, however, because it would be awesome to also have some input from a veteran player's perspective - I have very few experience with mahjong as a player sadly. – mafu Nov 26 '13 at 18:35
3

If you're eyeballing it, then you go with counting three things:

  • the standard count from 8. (worst case scenario: 147m147p147s1234z or 13-sided kokushi)
  • the terminal count from 13. (worst case scenario: all simples is 13-shanten)
  • the seven pairs count from 6. (worst case scenario: anything without a pair)

Standard count from 8 is to identify up to 5 groupings. Groups of 3 (subtracting 2) are always better than 2 groups of 2 (subtracting 1 each), as there is always an extra tile to pick up a spare from overlapping groups. If #groupings >= 5, then verify if one of the groups is a pair. Otherwise, you only have 4 "legitimate" groups. Protogroups of 2 are called taatsu (t), pairs are counted separately (p). Generally, shanten = 8 - 2*g - t - p. My code extracts pairs before protogroups when counting. With no complete groups, the best you can achieve with the standard count is 3-shanten.

Terminal count is easy. How many terminals, and how many different terminals. For live play, just look for a pair and ignore counting them all. shanten = 13 - diffTerminals() - if(numTerminals() > diffTerminals;1;0).

Pair count is also easy. shanten = 6 - p. Whichever of the 3 is smallest is your shanten count.


Now what the code does is that it can take a hand with size 1,2,4,5,7,8,10,11,13 or 14 and just break it down. When feeding less than 13 tiles, g can be pre-loaded with the number of melds formed. AFAIK, it won't count for self-walled protogroups if you feed it 45p55777z with 2 melds, and they happen to be two kans of 3333p and 6666p, it won't register that the hand is 1-shanten instead of tenpai... and same with 4578p if the 3 melds are 3333p6666p9999p, then the hand is 2-shanten, not 1 or 0. This is the edgiest of edge cases though.

What it does do though, is if it ends up tenpai, it will test adding every tile to get a complete hand (-1). However, it cannot add a tile already there. So even if one of the intermediate checks says it's fine, when a full hand is present, it will not validate. A hand with 4 of the same and 3 unrelated groups is by definition, not tenpai, as the only way it could "complete" is with a 5th copy of the same tile, which is not possible.

As for what elements to take, it cycles through everything. Could a group like 34555p be better as 345 + 55 or 34 + 555, especially if you need a pair amoing the non-grouped items? Maybe, but it will still cycle through all possibilities. The line detecting three of a kind will just shift eveerything forward by two tiles as there's no point in detecting if in tiles ABCDEFGHIJKLM: DGH, EGH and FGH make runs if you know DEF are the same. Just do DEF at that point, jump, check FGH and move on.

When I run this formula on cycles of random tile distributions, I get in the area of 220 tenhou hands per 100M, which is in line with Tenhou's monthly running stat pages.

  // INPUT: hand is a sorted array of tile values from 0-135 without repeating
  // INPUT: g is the number of groups. The function works recursively by extracting a group and calling itself with stc(smallerHand, g+1)
function stc(hand, g) {
  // INIT
  var retVal = 8; var t = 0; var p = 0;
  // KOKUSHI
  if (hand.length >= 13) retVal = Math.min(retVal, 13 - diffTerminals(hand) - Math.max(Math.min(terminalPairs(hand), 1),0));
  // SUBHAND RECURSION: Sorted hands have relevant tiles spaced 4 apart or less. Order: O{4*4*(n-5)} instead of O{(n^3 / 6}
  if (hand.length > 3) {
      for (i=0; i<hand.length-2; i++) {
      for (j=i+1; j<hand.length-1 && (j-i<5); j++) {
      for (k=j+1; k<hand.length && (k-j<5); k++) {
          // Max subhands:: 14 tiles: 6635520; 13 tiles: 1351680.
          if (isGroup(hand[i], hand[j], hand[k])) {
              var subHand = [];
              for (var m in hand) {subHand[m] = hand[m];}
              subHand.splice(k,1); subHand.splice(j,1); subHand.splice(i,1);
              retVal = Math.min(retVal, this.stc(subHand, g+1));
          }
          if (isPair(hand[i],hand[j]) && isPair(hand[i],hand[k])) i++; j=999; k=999;
      }}}
  }
  // PROTOGROUP COUNT
  if (hand.length > 1) {
      // Priority: AB pair, AC pair, AB taatsu
      for (i = 0; i<hand.length-1; i++) {
          if(isPair(hand[i], hand[i+1])) {p++;i++;}
          else if(i<hand.length-2 && isPair(hand[i], hand[i+2])) {p++;i=i+2;}
          else if(isTaatsu(hand[i], hand[i+1])) {t++;i++;}
      }
  }
  // 5 to 7 pairs, immeditate return on 7
  if (p==7) return -1; if (p>4&&hand.length>=13) retVal = Math.min(retVal, 6-p); 
  // Standard count: 8 - 2*g - max(4-g,p+t) - min(1,max(0,p+t-(4-g)))  
  if(p+t>Math.floor(hand.length/3)) {
      if (p>0) retVal = Math.min(retVal, 8 - 2*g - Math.floor(hand.length/3) - 1);  
      else retVal = Math.min(retVal, 8 - 2*g - Math.floor(hand.length/3) - 0);
  } else retVal = Math.min(retVal, 8 - 2*g - p - t, 6 - p);
  // Tenpai check for shanten == 0.
  if(retVal==0 && hand.length==13) {
      for(var i=0;i<136;i++){
        var checkHand = [];
        var checkRes = 0;
        for (var m in hand) {checkHand[m] = hand[m];}
        checkHand.push(i);
        for (var n = 0; n<13;n++) {
            if (checkHand[n] == i) {n=999;}
            if (n == 12) {
              checkHand = checkHand.sort(doCompare);
              checkRes = stc(checkHand,0);
            }
        }
        retVal = Math.min(retVal, checkRes + 1); // checkRes would be -1 if legit tenpai, 0 if not. **EDIT2016: I think the +1 should be out of the parentheses**
      }
  } 
  return retVal;
}
  • Thank you, this seems very closely in line with my results, which are derived purely from theory (I'm not playing Mahjong much myself). I'll consider your answer carefully. – mafu Aug 6 '16 at 19:35
1

If you're taking over a minute to count shanten you're thinking too hard. Shanten counting can be done in seconds using your head.
Look at your hand. For every pair or incomplete sequence, count 1. For completed melds, count 2. Subtract the total from 8. Do not count tiles with overlap!

Example: 1225dots 56bamboos 23588characters red east

12dots = 1
56bamboos = 1
23characters = 1
88characters = 1
total = 4
shanten = 8 - 4 = 4

  • Well, try something like 1233445567789m then :) – mafu Nov 26 '13 at 18:32
  • The general idea is close to what I'm doing right now though. – mafu Nov 26 '13 at 18:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.