Compare commits
3 Commits
45033a64a3
...
9be66d6cc0
Author | SHA1 | Date | |
---|---|---|---|
9be66d6cc0 | |||
f0e5b965bf | |||
b2980e01b5 |
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,2 +1,3 @@
|
||||||
node_modules
|
node_modules
|
||||||
config.json
|
config.json
|
||||||
|
data/HandRanks.dat
|
||||||
|
|
19
config.example.json
Normal file
19
config.example.json
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"clients": [{
|
||||||
|
"type": "irc",
|
||||||
|
"enabled": true,
|
||||||
|
"network": "n0xy",
|
||||||
|
"host": "",
|
||||||
|
"port": 6697,
|
||||||
|
"ssl": true,
|
||||||
|
"selfSigned": false,
|
||||||
|
"sasl": false,
|
||||||
|
"nickname": "schmirc",
|
||||||
|
"username": "schmirc",
|
||||||
|
"password": "",
|
||||||
|
"realname": "schmirc",
|
||||||
|
"channels": [
|
||||||
|
"#poker"
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
}
|
BIN
data/generate_table
Executable file
BIN
data/generate_table
Executable file
Binary file not shown.
9
package-lock.json
generated
9
package-lock.json
generated
|
@ -7,17 +7,14 @@
|
||||||
"": {
|
"": {
|
||||||
"name": "schmirc",
|
"name": "schmirc",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@xpressit/winning-poker-hand-rank": "^0.1.6",
|
|
||||||
"cuffeo": "^1.2.2"
|
"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": {
|
"node_modules/cuffeo": {
|
||||||
"version": "1.2.2",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/cuffeo/-/cuffeo-1.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/cuffeo/-/cuffeo-1.2.2.tgz",
|
||||||
|
|
|
@ -2,14 +2,17 @@
|
||||||
"name": "schmirc",
|
"name": "schmirc",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.mjs",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node ./src/index.mjs"
|
"start": "node ./src/index.mjs",
|
||||||
|
"build": "cd ./data && ./generate_table"
|
||||||
},
|
},
|
||||||
"author": "Flummi",
|
"author": "Flummi",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@xpressit/winning-poker-hand-rank": "^0.1.6",
|
|
||||||
"cuffeo": "^1.2.2"
|
"cuffeo": "^1.2.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,44 @@
|
||||||
export default new class handranker {
|
import fs from 'node:fs';
|
||||||
constructor() {
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
export default new class handranker {
|
||||||
|
#ranks;
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
return {
|
||||||
|
handType: p >> 12,
|
||||||
|
handRank: p & 0x00000fff,
|
||||||
|
value: p,
|
||||||
|
handName: this.handtypes[p >> 12]
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
195
src/index.mjs
195
src/index.mjs
|
@ -1,5 +1,4 @@
|
||||||
import cuffeo from 'cuffeo';
|
import cuffeo from 'cuffeo';
|
||||||
import { rankHands } from '@xpressit/winning-poker-hand-rank';
|
|
||||||
import handranker from './inc/handranker.mjs';
|
import handranker from './inc/handranker.mjs';
|
||||||
import cfg from '../config.json' assert { type: 'json' };
|
import cfg from '../config.json' assert { type: 'json' };
|
||||||
|
|
||||||
|
@ -8,6 +7,7 @@ const bot = await new cuffeo(cfg.clients);
|
||||||
const suits = { "♠": "S", "♣": "C", "♦": "D", "♥": "H" };
|
const suits = { "♠": "S", "♣": "C", "♦": "D", "♥": "H" };
|
||||||
|
|
||||||
const stripColors = msg => msg.replace(/\x03\d{0,2}(,\d{0,2}|\x02\x02)?/g, '');
|
const stripColors = msg => msg.replace(/\x03\d{0,2}(,\d{0,2}|\x02\x02)?/g, '');
|
||||||
|
const rand = (max = 1) => ~~(Math.random() * (max - 1) + 1);
|
||||||
|
|
||||||
const parseCards = msg => {
|
const parseCards = msg => {
|
||||||
const output = [];
|
const output = [];
|
||||||
|
@ -21,22 +21,34 @@ const parseCards = msg => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const env = {
|
const env = {
|
||||||
|
gamestate: 'preflop', // [preflop,flop,turn,river]
|
||||||
hand: false,
|
hand: false,
|
||||||
board: false,
|
board: false,
|
||||||
winchance: false,
|
winchance: false,
|
||||||
called: false,
|
|
||||||
joined: false,
|
joined: false,
|
||||||
callamount: 0,
|
callamount: 0,
|
||||||
pot: 0,
|
pot: 0,
|
||||||
bank: 0
|
bank: 0,
|
||||||
|
lastaction: false // debug purpose
|
||||||
};
|
};
|
||||||
|
|
||||||
|
bot.on("notice", msg => {
|
||||||
|
if(msg.match(/^Your hand/)) {
|
||||||
|
env.hand = parseCards(msg);
|
||||||
|
console.log('hand:', env.hand);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
bot.on("message", e => {
|
bot.on("message", e => {
|
||||||
|
if(e.channel !== '#poker') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if(e.message.match(new RegExp(`^${e.self.me.nickname}: Your bank account`))) {
|
if(e.message.match(new RegExp(`^${e.self.me.nickname}: Your bank account`))) {
|
||||||
env.bank = +e.message.match(/is: (\d+) \(/)[1];
|
env.bank = +e.message.match(/is: (\d+) \(/)[1];
|
||||||
}
|
}
|
||||||
if(e.message === ".pj" || e.message === "pj" && !env.hand && !env.joined) {
|
if(e.message === ".pj" || e.message === "pj" && !env.hand && !env.joined) {
|
||||||
env.joined = true;
|
env.joined = true;
|
||||||
|
//return e.reply("pj");
|
||||||
return e.reply("bb\npj");
|
return e.reply("bb\npj");
|
||||||
}
|
}
|
||||||
if(e.message === ".pl" && !env.hand) {
|
if(e.message === ".pl" && !env.hand) {
|
||||||
|
@ -57,104 +69,113 @@ bot.on("message", e => {
|
||||||
if(e.user.nick !== 'hirc' || !env.hand)
|
if(e.user.nick !== 'hirc' || !env.hand)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if(e.message.match(/(small|big) blind/)) {
|
// tracker
|
||||||
env.callamount = +e.message.match(/pays (\d+) \(/)[1];
|
|
||||||
env.pot += env.callamount;
|
|
||||||
if(e.message.includes(e.self.me.nickname)) {
|
|
||||||
env.bank -= +e.message.match(/pays (\d+) \(/)[1];
|
|
||||||
}
|
|
||||||
console.log(`callamount: ${env.callamount}; potsize: ${env.pot}; winchance: ${env.winchance}`);
|
|
||||||
}
|
|
||||||
if(e.message.includes('raises the pot')) {
|
if(e.message.includes('raises the pot')) {
|
||||||
env.callamount = +e.message.match(/pot by (\d+)/)[1];
|
env.callamount = +e.message.match(/raises the pot by (\d+)/)[1];
|
||||||
env.pot += env.callamount;
|
env.pot += env.callamount;
|
||||||
console.log(`callamount: ${env.callamount}; potsize: ${env.pot}; winchance: ${env.winchance}`);
|
if(e.message.includes(e.self.me.nickname))
|
||||||
|
env.bank -= env.callamount;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if(e.message.match(/calls \d+\./)) {
|
if(e.message.includes('calls')) {
|
||||||
env.callamount = +e.message.match(/calls (\d+)\./)[1];
|
env.callamount = +e.message.match(/calls (\d+)\./)[1];
|
||||||
env.pot += env.callamount;
|
env.pot += env.callamount;
|
||||||
console.log(`callamount: ${env.callamount}; potsize: ${env.pot}; winchance: ${env.winchance}`);
|
if(e.message.includes(e.self.me.nickname))
|
||||||
}
|
|
||||||
if(e.message.includes('check')) {
|
|
||||||
console.log(`callamount: ${env.callamount}; potsize: ${env.pot}; winchance: ${env.winchance}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(e.message.match(new RegExp(`^Current player: ${e.self.me.nickname}( |$)`)) && env.hand) {
|
|
||||||
if(!env.winchance) { // preflop
|
|
||||||
const callby = +e.message.match(/\((\d+) to call\)/)?.[1] || 0;
|
|
||||||
if(callby > 0) {
|
|
||||||
env.bank -= callby;
|
|
||||||
}
|
|
||||||
return e.reply('c');
|
|
||||||
}
|
|
||||||
|
|
||||||
if(env.winchance >= 55 && env.callamount) {
|
|
||||||
if(~~(Math.random() * 2) === 1) {
|
|
||||||
const raiseby = env.callamount + (~~(Math.random() * 2 + 2) * 10);
|
|
||||||
env.bank -= raiseby;
|
|
||||||
return e.reply(`r ${raiseby}`);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return e.reply('c');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(e.message.includes('to call')) {
|
|
||||||
if(env.winchance >= 30) {
|
|
||||||
if(env.called) {
|
|
||||||
env.bank -= env.callamount;
|
env.bank -= env.callamount;
|
||||||
return e.reply('c');
|
return;
|
||||||
}
|
}
|
||||||
else {
|
if(e.message.endsWith('blind).')) { // blinds
|
||||||
env.called = true;
|
env.callamount = +e.message.match(/pays (\d+) \(/)[1];
|
||||||
|
env.pot += env.callamount;
|
||||||
if((~~(Math.random() * 2)) === 1) {
|
env.gamestate = 'preflop';
|
||||||
|
if(e.message.includes(e.self.me.nickname))
|
||||||
env.bank -= env.callamount;
|
env.bank -= env.callamount;
|
||||||
return e.reply('c');
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return e.reply('f');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if((~~Math.random() * 5) === 1) {
|
|
||||||
return e.reply('f');
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
env.bank -= env.callamount;
|
|
||||||
return e.reply('c');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
env.bank -= env.callamount;
|
|
||||||
return e.reply('c');
|
|
||||||
}
|
|
||||||
if(e.message.includes('Game ended') || e.message.includes('wins the pot')) {
|
|
||||||
//e.reply(`my hand: ${hand.join(', ')} (${winchance}%)`);
|
|
||||||
e.reply('my bank balance: ' + env.bank);
|
|
||||||
env.hand = false;
|
|
||||||
env.board = false;
|
|
||||||
env.winchance = false;
|
|
||||||
env.called = false;
|
|
||||||
env.joined = false;
|
|
||||||
env.pot = 0;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(e.message.match(/^(Flop|Turn|River)/) && e.user.nick === 'hirc') {
|
// 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
|
||||||
|
env.lastaction = action;
|
||||||
|
return e.reply(action); // checkcall
|
||||||
|
}
|
||||||
|
|
||||||
|
if(e.message.endsWith('to call)')) { // callphase
|
||||||
|
env.callamount = +e.message.match(/\((\d+) to call\)/)[1];
|
||||||
|
|
||||||
|
if(env.winchance < 4300) {
|
||||||
|
if(rand(5) === 1 && env.callamount < 80) { // bad hand, call anyway
|
||||||
|
action = 'c';
|
||||||
|
}
|
||||||
|
else { // bad hand, fold
|
||||||
|
action = 'f';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(env.winchance > 7000) { // decent hand, raise
|
||||||
|
if(rand(5) === 1) { // 20%
|
||||||
|
action = 'r ' + (env.callamount + rand(5) * 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(env.winchance > 15000) { // good hand lol
|
||||||
|
if(rand(2) === 1) { // 50%
|
||||||
|
action = 'r ' + (env.callamount + rand(6) * 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(env.winchance > 20000) { // fuck them all
|
||||||
|
action = 'r ' + (env.callamount + rand(7) * 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else { // checkphase
|
||||||
|
if(env.winchance > 7000) { // decend hand, raise
|
||||||
|
if(rand(5) === 1) { // 20%
|
||||||
|
action = 'r ' + (env.callamount + rand(5) * 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(env.winchance > 15000) { // good hand lol
|
||||||
|
if(rand(2) === 1) { // 50%
|
||||||
|
action = 'r ' + (env.callamount + rand(6) * 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(env.winchance > 20000) { // fuck them all
|
||||||
|
action = 'r ' + (env.callamount + rand(7) * 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(action) {
|
||||||
|
env.lastaction = action;
|
||||||
|
return e.reply(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// gamestate & board changes
|
||||||
|
if(e.message.match(/^(Flop|Turn|River)/)) {
|
||||||
env.board = parseCards(e.message);
|
env.board = parseCards(e.message);
|
||||||
|
env.gamestate = e.message.match(/(\w+): /)[1].toLowerCase();
|
||||||
|
|
||||||
console.log(env.board, env.hand);
|
const rank = handranker.evalHand([...env.board, ...env.hand]);
|
||||||
|
env.winchance = rank.value;
|
||||||
const rank = rankHands('texas', env.board, [env.hand])[0];
|
|
||||||
env.winchance = (9999 - rank.rank) / 100;
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
bot.on("notice", msg => {
|
// end of game
|
||||||
if(msg.match(/^Your hand/)) {
|
if(e.message.includes('wins the pot') || e.message.includes('Split pot')) {
|
||||||
env.hand = parseCards(msg);
|
if(e.message.includes(e.self.me.nickname)) {
|
||||||
|
const amount = +e.message.match(/size (\d+) /)[1];
|
||||||
|
if(e.message.includes('Split pot'))
|
||||||
|
env.bank += ~~(amount / 2);
|
||||||
|
else
|
||||||
|
env.bank += amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
env.gamestate = 'preflop';
|
||||||
|
env.hand = false;
|
||||||
|
env.board = false;
|
||||||
|
env.winchance = false;
|
||||||
|
env.joined = false;
|
||||||
|
env.pot = 0;
|
||||||
|
env.lastaction = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(env);
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue
Block a user