uff #1
@@ -3,8 +3,6 @@
 | 
			
		||||
## Installation & Usage
 | 
			
		||||
npm i
 | 
			
		||||
 | 
			
		||||
npm run build
 | 
			
		||||
 | 
			
		||||
cp config.example.json config.json
 | 
			
		||||
 | 
			
		||||
edit config.json
 | 
			
		||||
 
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										6
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										6
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -12,9 +12,15 @@
 | 
			
		||||
      ],
 | 
			
		||||
      "license": "ISC",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@xpressit/winning-poker-hand-rank": "^0.1.6",
 | 
			
		||||
        "cuffeo": "^1.2.2"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@xpressit/winning-poker-hand-rank": {
 | 
			
		||||
      "version": "0.1.6",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@xpressit/winning-poker-hand-rank/-/winning-poker-hand-rank-0.1.6.tgz",
 | 
			
		||||
      "integrity": "sha512-l7b8GAKOT6k79qKF/SesCgQLvCjHZkhihf5QhgcL9w3hiya2JeCVyg07TVayoyO8PzDq56MH+yKk5rcbDMYScw=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/cuffeo": {
 | 
			
		||||
      "version": "1.2.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/cuffeo/-/cuffeo-1.2.2.tgz",
 | 
			
		||||
 
 | 
			
		||||
@@ -7,8 +7,7 @@
 | 
			
		||||
    "x64"
 | 
			
		||||
  ],
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "start": "node ./src/index.mjs",
 | 
			
		||||
    "build": "cd ./data && ./generate_table"
 | 
			
		||||
    "start": "node ./src/index.mjs"
 | 
			
		||||
  },
 | 
			
		||||
  "author": "Flummi",
 | 
			
		||||
  "license": "ISC",
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1410
									
								
								src/inc/constants.mjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1410
									
								
								src/inc/constants.mjs
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,44 +1,84 @@
 | 
			
		||||
import fs from 'node:fs';
 | 
			
		||||
import constants from './constants.mjs';
 | 
			
		||||
 | 
			
		||||
export default new class handranker {
 | 
			
		||||
  #ranks;
 | 
			
		||||
  rankHands(board, hand) {
 | 
			
		||||
    let bestHand = this.calcBestHand([...board, ...hand].map(c => this.toCard(c)));
 | 
			
		||||
 | 
			
		||||
  constructor() {
 | 
			
		||||
    this.#ranks = fs.readFileSync('./data/HandRanks.dat');
 | 
			
		||||
 | 
			
		||||
    this.handtypes = [
 | 
			
		||||
      "InvalidHand", "HighCard", "Pair",
 | 
			
		||||
      "TwoPairs", "ThreeOfAKind", "Straight",
 | 
			
		||||
      "Flush", "FullHouse", "FourOfAKind",
 | 
			
		||||
      "StraightFlush"
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    this.cards = [''];
 | 
			
		||||
    for(const cv of [..."23456789TJQKA"])
 | 
			
		||||
      for(const cs of [..."CDHS"])
 | 
			
		||||
        this.cards.push(cv + cs);
 | 
			
		||||
  };
 | 
			
		||||
  evalHand(cards) {
 | 
			
		||||
    if(!this.#ranks)
 | 
			
		||||
      throw new Error("HandRanks.dat not loaded, run 'npm run build' first!");
 | 
			
		||||
 | 
			
		||||
    if(![7,6,5,3].includes(cards.length))
 | 
			
		||||
      throw new Error("Hand must be 3, 5, 6, or 7 cards");
 | 
			
		||||
 | 
			
		||||
    cards = cards.map(c => this.cards.indexOf(c));
 | 
			
		||||
 | 
			
		||||
    let p = 53;
 | 
			
		||||
    for(let i = 0; i < cards.length; i++)
 | 
			
		||||
      p = this.#ranks.readUInt32LE((p + cards[i]) * 4);
 | 
			
		||||
 | 
			
		||||
    if(cards.length == 5 || cards.length == 6)
 | 
			
		||||
      p = this.#ranks.readUInt32LE(p * 4);
 | 
			
		||||
    if(bestHand > 6000) bestHand += 2000;
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
      handType: p >> 12,
 | 
			
		||||
      handRank: p & 0x00000fff,
 | 
			
		||||
      value: p,
 | 
			
		||||
      handName: this.handtypes[p >> 12]
 | 
			
		||||
      rank: bestHand,
 | 
			
		||||
      percentage: (9999 - bestHand) / 100,
 | 
			
		||||
      combination: this.toCombination(bestHand)
 | 
			
		||||
    };
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  toCombination(r) {
 | 
			
		||||
    if(r > 6185) return 'HighCard';
 | 
			
		||||
    if(r > 3325) return 'Pair';
 | 
			
		||||
    if(r > 2467) return 'TwoPairs';
 | 
			
		||||
    if(r > 1609) return 'ThreeOfAKind';
 | 
			
		||||
    if(r > 1599) return 'Straight';
 | 
			
		||||
    if(r > 322)  return 'Flush';
 | 
			
		||||
    if(r > 166)  return 'FullHouse';
 | 
			
		||||
    if(r > 10)   return 'FourOfAKind';
 | 
			
		||||
    return 'StraightFlush';
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  cactusFastRankHand(hand) {
 | 
			
		||||
    const [c0, c1, c2, c3, c4] = hand;
 | 
			
		||||
    if((c0 & c1 & c2 & c3 & c4 & 0xf000) !== 0)
 | 
			
		||||
      return constants.fastFlushes[(c0 | c1 | c2 | c3 | c4) >>> 16];
 | 
			
		||||
    const r = constants.fastUnique5[(c0 | c1 | c2 | c3 | c4) >>> 16];
 | 
			
		||||
    if(r)
 | 
			
		||||
      return r;
 | 
			
		||||
    let u = 0xe91aaa35 + (((c0 & 0xff) * (c1 & 0xff) * (c2 & 0xff) * (c3 & 0xff) * (c4 & 0xff)) | 0);
 | 
			
		||||
    u = u ^ (u >>> 16);
 | 
			
		||||
    u += u << 8;
 | 
			
		||||
    u ^= u >>> 4;
 | 
			
		||||
    return constants.hash[((u + (u << 2)) >>> 19) ^ (constants.hashAdjust[(u >>> 8) & 0x1ff] | 0)];
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  calcBestHand(hand) {
 | 
			
		||||
    if(hand.length === 5)
 | 
			
		||||
      return this.cactusFastRankHand([hand[0], hand[1], hand[2], hand[3], hand[4]])
 | 
			
		||||
    if(hand.length === 6) {
 | 
			
		||||
      const possibleHands = [
 | 
			
		||||
        [hand[0], hand[1], hand[2], hand[3], hand[4]],
 | 
			
		||||
        [hand[0], hand[1], hand[2], hand[3], hand[5]],
 | 
			
		||||
        [hand[0], hand[1], hand[2], hand[4], hand[5]],
 | 
			
		||||
        [hand[0], hand[1], hand[3], hand[4], hand[5]],
 | 
			
		||||
        [hand[0], hand[2], hand[3], hand[4], hand[5]],
 | 
			
		||||
        [hand[1], hand[2], hand[3], hand[4], hand[5]],
 | 
			
		||||
      ];
 | 
			
		||||
      const sortedHands = possibleHands.map(h => this.cactusFastRankHand(h)).sort();
 | 
			
		||||
      return sortedHands[0];
 | 
			
		||||
    }
 | 
			
		||||
    if(hand.length === 7) {
 | 
			
		||||
      let r = 0;
 | 
			
		||||
      let rank = 9999;
 | 
			
		||||
      for(let i = 0; i < 21; i++) {
 | 
			
		||||
        const inputHand = [
 | 
			
		||||
          hand[constants.t7c5[i][0]],
 | 
			
		||||
          hand[constants.t7c5[i][1]],
 | 
			
		||||
          hand[constants.t7c5[i][2]],
 | 
			
		||||
          hand[constants.t7c5[i][3]],
 | 
			
		||||
          hand[constants.t7c5[i][4]],
 | 
			
		||||
        ];
 | 
			
		||||
        r = this.cactusFastRankHand(inputHand);
 | 
			
		||||
        if(r < rank)
 | 
			
		||||
          rank = r;
 | 
			
		||||
      }
 | 
			
		||||
      return rank;
 | 
			
		||||
    }
 | 
			
		||||
    throw `Hand ranker doesn't support ${hand.length} cards`;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  toCard(playingCard) {
 | 
			
		||||
    const rank = constants.runeToRank[playingCard[0]];
 | 
			
		||||
    const suit = constants.runeToSuit[playingCard[1]];
 | 
			
		||||
    if(!suit || rank === undefined)
 | 
			
		||||
      throw `Invalid playing card: ${playingCard}`;
 | 
			
		||||
    return ((1 << rank) << 16) | (suit << 12) | (rank << 8) | constants.PRIMES[rank];
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -6,10 +6,10 @@ const cfg = helper.config;
 | 
			
		||||
export const bot = await new cuffeo(cfg.getFull().clients);
 | 
			
		||||
 | 
			
		||||
export const env = {
 | 
			
		||||
  gamestate: 'notstarted, ' + (cfg.get('autojoin') ? 'autojoin' : 'no autojoin'), // [notstarted,preflop,flop,turn,river]
 | 
			
		||||
  gamestate: 'not started, ' + (cfg.get('autojoin') ? 'autojoin' : 'no autojoin'),
 | 
			
		||||
  hand: false,
 | 
			
		||||
  board: false,
 | 
			
		||||
  winchance: false,
 | 
			
		||||
  odds: false,
 | 
			
		||||
  joined: false,
 | 
			
		||||
  callamount: 0,
 | 
			
		||||
  pot: 0,
 | 
			
		||||
@@ -28,6 +28,29 @@ bot.on("notice", msg => {
 | 
			
		||||
bot.on("message", async e => {
 | 
			
		||||
  if(e.channel !== cfg.get('channel'))
 | 
			
		||||
    return;
 | 
			
		||||
  if(e.message.startsWith(".cards ")) {
 | 
			
		||||
    let cards = [];
 | 
			
		||||
    try {
 | 
			
		||||
      cards = JSON.parse(e.message.slice(7));
 | 
			
		||||
    } catch(err) {
 | 
			
		||||
      return e.reply('das ist kein Array du Pflaumennase');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const hand = cards.slice(0, 2);
 | 
			
		||||
    const board = cards.slice(2);
 | 
			
		||||
 | 
			
		||||
    let rank;
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      rank = handranker.rankHands(board, hand);
 | 
			
		||||
    } catch(err) {
 | 
			
		||||
      return e.reply(err);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    e.reply(JSON.stringify(rank));
 | 
			
		||||
    
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if(e.message === `.${e.self.me.nickname} help`) {
 | 
			
		||||
    await e.write(`PRIVMSG ${e.user.nick} I always say hirc schmirc, available commands are:`);
 | 
			
		||||
    const commands = [
 | 
			
		||||
@@ -137,7 +160,7 @@ bot.on("message", async e => {
 | 
			
		||||
  // bot's turn
 | 
			
		||||
  if(e.message.match(new RegExp(`Current player: ${e.self.me.nickname}( |$)`))) {
 | 
			
		||||
    let action = 'c'; // default
 | 
			
		||||
    if(env.gamestate === 'preflop' || !env.winchance) { // preflop
 | 
			
		||||
    if(env.gamestate === 'preflop' || !env.odds) { // preflop
 | 
			
		||||
      env.lastaction = action;
 | 
			
		||||
      return e.reply(action); // checkcall
 | 
			
		||||
    }
 | 
			
		||||
@@ -148,40 +171,40 @@ bot.on("message", async e => {
 | 
			
		||||
      if(env.callamount > env.bank) // not enough money lol
 | 
			
		||||
        return e.reply(['huan!', 'f']);
 | 
			
		||||
      
 | 
			
		||||
      if(env.winchance < 6500) {
 | 
			
		||||
        if(helper.rand(5) === 1 && env.callamount < (helper.rand(6,10) * 10)) { // bad hand, call anyway
 | 
			
		||||
      if(env.odds < 6500) {
 | 
			
		||||
        if(helper.rand(5) === 1 && env.callamount < (helper.rand(2, 6) * 10)) { // bad hand, call anyway
 | 
			
		||||
          action = 'c';
 | 
			
		||||
        }
 | 
			
		||||
        else { // bad hand, fold
 | 
			
		||||
          action = 'f';
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      else if(env.winchance > 7000) { // decent hand, raise
 | 
			
		||||
      else if(env.odds > 55) { // decent hand, raise
 | 
			
		||||
        if(helper.rand(5) === 1) { // 20%
 | 
			
		||||
          action = 'r ' + (env.callamount + helper.rand(5) * 10);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      else if(env.winchance > 15000) { // good hand lol
 | 
			
		||||
      else if(env.odds > 70) { // good hand lol
 | 
			
		||||
        if(helper.rand(2) === 1) { // 50%
 | 
			
		||||
          action = 'r ' + (env.callamount + helper.rand(6) * 10);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      else if(env.winchance > 20000) { // fuck them all
 | 
			
		||||
      else if(env.odds > 85) { // fuck them all
 | 
			
		||||
        action = 'r ' + (env.callamount + helper.rand(7) * 10);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    else { // checkphase
 | 
			
		||||
      if(env.winchance > 7000) { // decend hand, raise
 | 
			
		||||
      if(env.odds > 55) { // decend hand, raise
 | 
			
		||||
        if(helper.rand(5) === 1) { // 20%
 | 
			
		||||
          action = 'r ' + (env.callamount + helper.rand(5) * 10);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      else if(env.winchance > 15000) { // good hand lol
 | 
			
		||||
      else if(env.odds > 70) { // good hand lol
 | 
			
		||||
        if(helper.rand(2) === 1) { // 50%
 | 
			
		||||
          action = 'r ' + (env.callamount + helper.rand(6) * 10);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      else if(env.winchance > 20000) { // fuck them all
 | 
			
		||||
      else if(env.odds > 85) { // fuck them all
 | 
			
		||||
        action = 'r ' + (env.callamount + helper.rand(7) * 10);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
@@ -201,8 +224,8 @@ bot.on("message", async e => {
 | 
			
		||||
    if(cfg.get('debug'))
 | 
			
		||||
      e.reply(`${oldstate} -> ${env.gamestate}`);
 | 
			
		||||
 | 
			
		||||
    const rank = handranker.evalHand([...env.board, ...env.hand]);
 | 
			
		||||
    env.winchance = rank.value;
 | 
			
		||||
    const rank = handranker.rankHands(env.board, env.hand);
 | 
			
		||||
    env.odds = rank.percentage;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // end of game
 | 
			
		||||
@@ -221,13 +244,13 @@ bot.on("message", async e => {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const oldstate = env.gamestate;
 | 
			
		||||
    env.gamestate = 'notstarted, ' + (cfg.get('autojoin') ? 'autojoin' : 'no autojoin');
 | 
			
		||||
    env.gamestate = 'not started, ' + (cfg.get('autojoin') ? 'autojoin' : 'no autojoin');
 | 
			
		||||
    if(cfg.get('debug'))
 | 
			
		||||
      e.reply(`${oldstate} -> ${env.gamestate}`);
 | 
			
		||||
 | 
			
		||||
    env.hand = false;
 | 
			
		||||
    env.board = false;
 | 
			
		||||
    env.winchance = false;
 | 
			
		||||
    env.odds = false;
 | 
			
		||||
    env.joined = false;
 | 
			
		||||
    env.pot = 0;
 | 
			
		||||
    env.lastaction = false;
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user