4
\$\begingroup\$

This is the third rewrite of the poker bot I am writing. The first one was such a mess of if / elses that I could not deal with it. The second version was cluttered, and I had trouble following what happened but did work. I decided to scrap it for a new clean module based / more functional version but it seems to fall into the same traps.

This is my first major project in any language, and version 1 was my first JavaScript project ever. I tried to read some formatting articles such as the Google formatting guide and follow those but I haven't read any formal books yet. I should probably do that. Any recommendations would rock.

I was going to wait until it was finished to post here, however if there were any major changes to anything it would be easier to do before the rest of the functions were finished.

The project is in node.js using the Node - Steam library.

This is the main program that calls the module running the game. Some values are hard-coded in, which will have to change once I finish the first module and start on a second.

var fs = require('fs')
var Steam = require('steam')
var _ = require('lodash')

var startedRooms = []

if (fs.existsSync('servers')) {
    Steam.servers = JSON.parse(fs.readFileSync('servers'))
}

var bot = new Steam.SteamClient()

bot.logOn({
    accountName: 'SpencerFlem',
    password: 'Ap3rtur3',
    shaSentryfile: fs.readFileSync('sentryfile')
})

bot.on('loggedOn', function() {
    var mainChat = '103582791434524271'
    console.log('Logged in!')
    bot.setPersonaName('Dealer')
    bot.setPersonaState(Steam.EPersonaState.Online); // to display your bot's status as "Online"
    bot.joinChat(mainChat);
})

bot.on('servers', function(servers) {
    fs.writeFile('servers', JSON.stringify(servers))
})

bot.on('chatEnter', function(room) {
    startupRoom(room)
})

// MAIN

var allData = {}

gameLookup = {
    '103582791434524271':'./Games/Poker/5 Card Draw/5 Card Draw.js' // This to the variable to use functions from?
}
var room103582791434524271 = require('./Games/Poker/5 Card Draw/5 Card Draw.js') // Object / array instead of variable? idk if possible 
// OR: require all by default and use its name as variable

function setupData(room) {
    condensedUsers = {}
    for (var i = 0; i < Object.keys(bot.chatRooms[room]).length; i++ ) {
        condensedUsers[Object.keys(bot.chatRooms[room])[i]] = bot.users[Object.keys(bot.chatRooms[room])[i]]
    }

    allData[room].users = condensedUsers
    allData[room].steamID = bot.steamID
    allData[room].thisChatRoom = bot.chatRooms[room]
    allData[room].friends = 'NONEXISTANT' //Make this not so!
    allData[room].stored = {}

}

var players = []

function startupRoom(room) { // If went offline and rejoined done restart room?
    allData[room] = {}
    setupData(room)
    progress = room103582791434524271.startup(allData[room])
    applyProgress(progress, room)
}

function applyProgress(progress, room) {
    allData[room].stored = progress.storedData
    commandList = Object.keys(progress.commands)
    if (commandList.indexOf('sendMessage') !== -1) {
        for (var i = 0; i < progress.commands.sendMessage.length; i++) {
            if (progress.commands.sendMessage[i][0] === 'room') { //why cant it know its room?
                bot.sendMessage(room, progress.commands.sendMessage[i][1])
            }
            else {
                bot.sendMessage(progress.commands.sendMessage[i][0], progress.commands.sendMessage[i][1])
            }
        }
    }
    if (commandList.indexOf('lockChat') !== -1) {
        if (lockChat === true) {
            bot.lockChat(room)
        }
    }
    if (commandList.indexOf('unlockChat') !== -1) {
        if (unlockChat === true) {
            bot.unlockChat(room)
        }
    }
    if (commandList.indexOf('setModerated') !== -1) {
        if (setModerated === true) {
            bot.setModerated(room)
        }
    }
    if (commandList.indexOf('setUnmoderated') !== -1) {
        if (setUnmoderated === true) {
            bot.setUnmoderated(room)
        }
    }
    if (commandList.indexOf('kick') !== -1) {
        for (var i = 0; i < progress.commands.kick.length; i++) {
            bot.kick(room, progress.commands.kick[i])
        }
    }
    if (commandList.indexOf('ban') !== -1) {
        for (var i = 0; i < progress.commands.ban.length; i++) {
            bot.ban(room, progress.commands.ban[i])
        }
    }
    if (commandList.indexOf('unban') !== -1) {
        for (var i = 0; i < progress.commands.unban.length; i++) {
            bot.unban(room, progress.commands.unban[i])
        }
    }
}

bot.on('chatStateChange', function(type, player, room, agent) {
    //console.log(type + '-' + player + '-' + room + '-' + agent)
})

bot.on('chatMsg', function(room, message, type, player) {
    progress = room103582791434524271.checkMsg(allData[room], message, type, player)
    applyProgress(progress, room)
    // Do something with progress, prehaps an eval function

})

Then, the real game part starts here

/// Invigorating Imports

var _ = require('lodash') //Not necessary? //so?
var fs = require('fs')

/// Sumptuous Variables

var data = {}
var roomData = {}
var playerData = {}

/// Useful Functions

var symbolNumbers = ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A']
var symbolSuits = ['♣', '♦', '♥', '♠']
var wordNumbers = ['Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine', 'Ten', 'Jack', 'Queen', 'King', 'Ace']
var wordSuits = ['Clubs', 'Diamonds', 'Hearts', 'Spades']

function setupLookup(numbers,suits) {
    var tempLookup = []
    for (var i=0; i<numbers.length; i++) {
        for (var j=0; j<suits.length; j++) {
            tempLookup.push([numbers[i],suits[j]])
        }
    }
    return tempLookup
}

var symbolLookup = setupLookup(symbolNumbers,symbolSuits)
var wordLookup = setupLookup(wordNumbers,wordSuits)

function extractNumbers(message) { //run regex and check for each middle part to get in order?
    var regex = /\D+/
    var numbers = []
    numbers = message.split(regex)
    _.pull(numbers, '') // !!! Commas ex: 1,984 would be 1 & 984 not: 1984 :(  Also 1.5 or 1 and 1/2 would be weird

    // Copypasta from old bot incoming

    var numbersZeroToNine = ['ZERO','ONE','TWO','THREE','FOUR','FIVE','SIX','SEVEN','EIGHT','NINE']
    var numbersTenToNineteen = ['TEN','ELEVEN','TWELVE','THIRTEEN','FOURTEEN','FIFTEEN','SIXTEEN','SEVENTEEN','EIGHTEEN','NINETEEN']
    var numbersTwentyToNinety = ['ZERO','TEN','TWENTY','THIRTY','FORTY','FIFTY','SIXTY','SEVENTY','EIGHTY','NINETY']
    for (i = 1 ; i < numbersTwentyToNinety.length ; i++) {
        if (message.indexOf(numbersTwentyToNinety[i]) != -1) {
            submessage = message.substr(message.indexOf(numbersTwentyToNinety[i]))
            for (j = 1 ; j < numbersZeroToNine.length ; j++) {
                if (submessage.indexOf(numbersZeroToNine[j]) != -1) {
                    numberBet = i*10+j
                    numbers.push(numberBet) //numberBet is bad name
                }
                else if (j == numbersZeroToNine.length - 1) {
                    numberBet = i*10
                    numbers.push(numberBet)
                }
            }
        }
    }
    for (k = 0 ; k < numbersTenToNineteen.length ; k++) {
        if (message.indexOf(numbersTenToNineteen[k]) != -1) {
            numberBet = k+10
            numbers.push(numberBet)
        }
    }
    for (l = 0 ; l < numbersZeroToNine.length ; l++) {
        if (message.indexOf(numbersZeroToNine[l]) != -1) {
            numberBet = l
            numbers.push(numberBet)
        }       
    }
    // </copypasta>

    console.log('NUMBERSNUMBERSNUMBERS---' + numbers)
    return numbers // Array should be in order of distance, right now is not due to copypasta
}

function formatHand(player) { //unfinshed //Also, add if increase

    var optionsList = []

    if (player === roomData.turn) {
        if (playerData[player].mustSpecifyBetAmount === true) {
            optionsList.push('SAY HOW MUCH YOU BET')
        }
        else {
            if (playerData[player].canBet === true) { optionsList.push('Bet') }
            if (playerData[player].canCheck === true) { optionsList.push('Check') }
            if (playerData[player].canAllIn === true) { optionsList.push('All In (' + playerData[player].wallet + ')') }
            if (playerData[player].canRaise === true) { optionsList.push('Raise (#)') }
            if (playerData[player].canCall === true) { optionsList.push('Call (' + (roomData.currentBet - playerData[player].amountBet) + ')') }
            if (playerData[player].canFold === true) { optionsList.push('Fold') }
            if (playerData[player].canShow === true) { optionsList.push('Yes'); optionsList.push('No')} 
        }
    }
    else {
        optionsList.push('AWAIT YOUR TURN')
    }
    var shortHand = ''
    for (var i = 0; i < playerData[player].hand.length; i++) {
        shortHand += symbolLookup[ playerData[player].hand[i] ][0] 
        shortHand += symbolLookup[ playerData[player].hand[i] ][1]
        if (i !== playerData[player].hand.length - 1) {
            shortHand += '  '
        }
    }
    var shortOptions = ''
    for (var i = 0; i < optionsList.length; i++) {
        shortOptions += optionsList[i]
        if (i !== optionsList.length - 1) {
            shortOptions += ' | '
        }
    }
    var longHand = '   '
    for (var i = 0; i < playerData[player].hand.length; i++) {
        longHand += '         '
        longHand += symbolLookup[ playerData[player].hand[i] ][0]
        longHand += symbolLookup[ playerData[player].hand[i] ][1]       
    }

    var statusSpaces = ''
    var statusNumberList = ''
    statusNumberList += roomData.currentBet.toString() + roomData.pot.toString() + playerData[player].wallet.toString()

    var statusSpacesNumber = Math.floor((27-(statusNumberList.length * 2)) / 4 )
    for (var i = 0; i < statusSpacesNumber; i++ ) {
        statusSpaces += ' '
    }
    var statusBar = ''
    statusBar = statusSpaces + 'CURRENT BET: ' + roomData.currentBet + statusSpaces + 'YOUR WALLET: ' + playerData[player].wallet + statusSpaces + 'POT: ' + roomData.pot

    var longOptions = ''
    var optionsSize = 0
    for (var i = 0; i < optionsList.length; i++) {
        optionsSize += optionsList[i].length * 2
    }

    var longOptionsSpaces = ''
    var longOptionsSpacesSize = Math.floor( (91 - optionsSize) / (optionsList.length * 2) )
    for (var i = 0; i < longOptionsSpacesSize; i++) {
        longOptionsSpaces += ' '
    }

    for (var i = 0; i < (optionsList.length); i++ ) {
        if (i === 0) {
            longOptions += longOptionsSpaces + optionsList[i]
        }
        else {
            longOptions += longOptionsSpaces + '|' + longOptionsSpaces + optionsList[i]
        }
    }

    var specialMessage = '.'

    message = shortHand + '\n' + shortOptions + '\n' + '———————————————————————————' + '\n' + '.' + '\n' + longHand + '\n' + '.' + '\n' + '———————————————————————————' 
    + '\n' + statusBar + '\n' + '———————————————————————————' + '\n' + specialMessage + '\n' + longOptions
    return [player, message]
}

    /// PLAY FUNCTIONS BEGIN HERE

function check() {
    var messages = []
    if (roomData.nextTurn === roomData.originalTurn) {
        messages.push(['room', 'Everybody loses. Try harder next time.'])

        //EVERYONE LOSES

    }
    else {
        messages.push(['room', data.users[roomData.turn].playerName + ' checked.'])
        roomData.advanceTurn()
    }
    messages.push(formatHand(roomData.turn))
    if (roomData.turn !== roomData.nextTurn) {
        messages.push(formatHand(roomData.nextTurn))
    }
    return messages
}

function bet(amount) { //unfinished
    var messages = []
    if (amount.length === 0) {
        playerData[roomData.turn].mustSpecifyBetAmount = true
        messages.push(['room', 'How much do you bet?'])
        messages.push(formatHand(roomData.turn))
    }
    else if (amount.length > 1) {
        playerData[roomData.turn].mustSpecifyBetAmount = true
        var message = 'How much do you bet? You said '
        if (amount.length === 2) {
            message += 'both ' + amount[0] + ' and ' + amount[1] + '.'
        }
        else {
            for (var i = 0; i < amount.length; i++) {
                if (i === amount.length - 1) {
                    message += 'and ' + amount[i] + '.'
                }
                else {
                    message += amount[i] + ', '
                }
            }
        }
        messages.push(['room', message])
        messages.push(formatHand(roomData.turn))
    }
    else {
        playerData[roomData.turn].mustSpecifyBetAmount = false
        if (amount[0] === playerData[roomData.turn].wallet) {
            // ACTIVATE ALL IN
        }
        else if (amount[0] > playerData[roomData.turn].wallet) {
            messages.push(['room', 'You are too poor, please bet a little lower.'])
        }
        else {
            roomData.originalTurn = roomData.turn
            playerData[roomData.turn].wallet -= amount[0]
            roomData.pot += amount[0]
            roomData.currentBet = amount
            playerData[roomData.turn].amountBet = amount
            for (var i = 0; i < roomData.remainingPlayers.length; i++) {
                playerData[roomData.remainingPlayers[i]].canCheck = false
                playerData[roomData.remainingPlayers[i]].canBet = false
                playerData[roomData.remainingPlayers[i]].canRaise = true
                playerData[roomData.remainingPlayers[i]].canCall = true
                playerData[roomData.remainingPlayers[i]].canFold = true
            }
            messages.push(['room', data.users[roomData.turn].playerName + 'bet ' + amount + '.'])
            messages.push(formatHand(roomData.turn))
            messages.push(formatHand(roomData.nextTurn))
            roomData.advanceTurn()
        }
    }
    return messages
}

/// Interesting Exports (allowed to affect the real messages)

exports.startup = function(givenData) {

    data = givenData
    players = Object.keys(data.thisChatRoom) //shuffle?
    _.pull(players,data.steamID)


    /// OBJECT SETUP BEGINS HERE
    roomData = {
        deck: [],
        makeDeck: function() { roomData.deck = _.shuffle(_.range(0,52,1)) },
        pot: 0,
        currentBet: 0,
        players: players,
        remainingPlayers: players,
        get turn() {return roomData.remainingPlayers[0]},
        originalTurn: players[0], //used to be defined lower. Breaks?
        get nextTurn() {return _.last(roomData.remainingPlayers)},
        advanceTurn: function() { roomData.remainingPlayers.unshift(roomData.remainingPlayers.pop()) }
    }

    playerData.addPlayer = function(player) {
        this[player] = {}
        this[player].hand = []
        this[player].deal = function(amount) { // should this be here?
            for (var i = 0; i < amount; i++) { //could subtract for optimal awesome (var i = amount. . .)
                this.hand.push(roomData.deck.pop()) // check if possible 1st
            }
        }
        this[player].amountBet = 0
        this[player].wallet = 0 // REMOVE?
        this[player].canCheck = true
        this[player].canBet = true
        this[player].mustSpecifyBetAmount = false
        this[player].canCall = false
        this[player].canRaise = false
        this[player].canFold = false
        this[player].canAllIn = false
        this[player].canShow = false
    }

    /// OBJECT SETUP ENDS HERE

    roomData.makeDeck()
    setupLookup(symbolLookup,symbolNumbers,symbolSuits)
    setupLookup(wordLookup,wordNumbers,wordSuits)

    var messages = []

    for(var i=0; i < players.length; i++) {
        playerData.addPlayer(players[i])
        playerData[players[i]].deal(5)
        messages.push(formatHand(players[i]))
    }

    data.stored.roomData = roomData
    data.stored.playerData = playerData

    var commands = {}
    console.log('nn' + JSON.stringify(messages, null, 4))
    commands.sendMessage = messages

    /*
    other commands:
    .addFirend = [player1, player2]
    .lockChat = true
    .unlockChat = true
    .setModerated = true
    .setUnmoderated = true
    .kick = [player1, ...]
    .ban = [player1 ...]
    .unban = [player1, ...]
    */

    var progress = {}
    progress.storedData = data.stored
    progress.commands = commands

    return progress
}

exports.checkMsg = function(givenData, receivedMessage, type, player) { //unfinished
    data = givenData
    roomData = data.stored.roomData
    playerData = data.stored.playerData
    receivedMessage = receivedMessage.toUpperCase()
    var messages = []

    if (playerData[player].mustSpecifyBetAmount === true && roomData.turn) {
        if (receivedMessage.indexOf('CHECK') !== -1) { // better way than listing each one?
            messages.push(['room', 'Do you bet or check?'])
            playerData[player].mustSpecifyBetAmount = false
        }
        else {
            var amount = extractNumbers(receivedMessage)
            var localMessages = bet(amount)
            for (var i = 0; i < localMessages.length; i++) {
                messages.push(localMessages[i])
            }
        }
        messages.push(formatHand(player))
    }

    else {
        if (receivedMessage.indexOf('CHECK') !== -1 && roomData.turn === player && playerData[player].canCheck === true) { // if both?
            var localMessages = check()
            for (var i = 0; i < localMessages.length; i++) { // Array holding each to its required values and its outcome? Would solve both problem and allow for similar values
                messages.push(localMessages[i])
            }
        }
        if (receivedMessage.indexOf('BET') !== -1 && roomData.turn === player && playerData[player].canBet === true) {
            var amount = extractNumbers(receivedMessage)
            var localMessages = bet(amount)
            for (var i = 0; i < localMessages.length; i++) {
                messages.push(localMessages[i])
            }
        }
    }

    if(receivedMessage.indexOf('HIT ME') !== -1) {
        playerData[player].wallet += 10
        console.log(playerData[player].wallet)
        messages.push(formatHand(player))
    }

    console.log('mm' + JSON.stringify(messages, null, 4))
    data.stored.roomData = roomData
    data.stored.playerData = playerData
    var commands = {}
    commands.sendMessage = messages
    var progress = {}
    progress.storedData = data.stored
    progress.commands = commands
    return progress
}

The parts I am specifically worried about if / else monstrosities is where the chat is processed. Right now it is not too bad, but right now only checking and betting is possible. With raising, folding etc.. it is bound to get more complicated. It also isn't able to tell if the message contained two commands such as "bet check" which is a problem. According to the style guides ifs should only be 1 or two levels deep and functions should be shorter.

if (playerData[player].mustSpecifyBetAmount === true && roomData.turn) {
    if (receivedMessage.indexOf('CHECK') !== -1) { // better way than listing each one?
        messages.push(['room', 'Do you bet or check?'])
        playerData[player].mustSpecifyBetAmount = false
    }
    else {
        var amount = extractNumbers(receivedMessage)
        var localMessages = bet(amount)
        for (var i = 0; i < localMessages.length; i++) {
            messages.push(localMessages[i])
        }
    }
    messages.push(formatHand(player))
}

else {
    if (receivedMessage.indexOf('CHECK') !== -1 && roomData.turn === player && playerData[player].canCheck === true) { // if both?
        var localMessages = check()
        for (var i = 0; i < localMessages.length; i++) { // Array holding each to its required values and its outcome? Would solve both problem and allow for similar values
            messages.push(localMessages[i])
        }
    }
    if (receivedMessage.indexOf('BET') !== -1 && roomData.turn === player && playerData[player].canBet === true) {
        var amount = extractNumbers(receivedMessage)
        var localMessages = bet(amount)
        for (var i = 0; i < localMessages.length; i++) {
            messages.push(localMessages[i])
        }
    }
}

Possible ideas: I tried making an array of the trigger word, its conditions and the function to be executed which could be iterated through. Unfortinitely I could not figure out how to make the function return a value wihout it looking even messier.

\$\endgroup\$
  • \$\begingroup\$ See this link regarding scrapping a project and completely rewriting the code: joelonsoftware.com/articles/fog0000000069.html Hint: it's called Things You Should Never Do :) \$\endgroup\$ – bazola Sep 12 '14 at 16:29
  • \$\begingroup\$ @bazola, from reading the code it looks like OP copy pasted some things from the old bot into this version, so it's not a complete rewrite. \$\endgroup\$ – Malachi Sep 12 '14 at 16:59
  • 1
    \$\begingroup\$ @bazola and Malachi, it was about half of each. The original was completely differently organized, and most of the stuff was 'technically' retyped, but some parts were pretty much taken from the first draft. Thanks for the link though, I definitely wont rewrite this draft. \$\endgroup\$ – Spencer Fleming Sep 13 '14 at 20:42
7
\$\begingroup\$

All of these Nested if Statements can definitely be changed into one level if statements

if (commandList.indexOf('lockChat') !== -1) {
    if (lockChat === true) {
        bot.lockChat(room)
    }
}
if (commandList.indexOf('unlockChat') !== -1) {
    if (unlockChat === true) {
        bot.unlockChat(room)
    }
}
if (commandList.indexOf('setModerated') !== -1) {
    if (setModerated === true) {
        bot.setModerated(room)
    }
}
if (commandList.indexOf('setUnmoderated') !== -1) {
    if (setUnmoderated === true) {
        bot.setUnmoderated(room)
    }
}

so it would look like this when we add the nested if statements to the expression of the first.

if (commandList.indexOf('lockChat') !== -1 && lockChat === true) {
    bot.lockChat(room)
}
if (commandList.indexOf('unlockChat') !== -1 && unlockChat === true) {
    bot.unlockChat(room)
}
if (commandList.indexOf('setModerated') !== -1 && setModerated === true) {
    bot.setModerated(room)
}
if (commandList.indexOf('setUnmoderated') !== -1 && setUnmoderated === true) {
    bot.setUnmoderated(room)
}

While we are talking about shortening the if statements or making them cleaner, I assume that

  • lockChat
  • unlockChat
  • setModerated
  • setUnmoderated

are all boolean values.

If they are you can drop the === true part of those expressions.


I found a little blurb here

    if (receivedMessage.indexOf('BET') !== -1 && roomData.turn === player && playerData[player].canBet === true) {
        var amount = extractNumbers(receivedMessage)
        var localMessages = bet(amount)
        for (var i = 0; i < localMessages.length; i++) {
            messages.push(localMessages[i])
        }
    }

that you could eliminate a variable (middle man), amount you only use it once, just use extractNumbers(receivedMessage) inside bet(extractNumbers(receivedMessage)


other than these things, I am not sure that you can clean up those if/else statements, it looks like they are as clean as they can be while still doing what you want them to do.

I am sure that you could smoosh some things into functions

like this

if (receivedMessage.indexOf('CHECK') !== -1 && roomData.turn === player && playerData[player].canCheck === true) { // if both?
    var localMessages = check()
    for (var i = 0; i < localMessages.length; i++) { // Array holding each to its required values and its outcome? Would solve both problem and allow for similar values
        messages.push(localMessages[i])
    }
}
if (receivedMessage.indexOf('BET') !== -1 && roomData.turn === player && playerData[player].canBet === true) {
    var amount = extractNumbers(receivedMessage)
    var localMessages = bet(amount)
    for (var i = 0; i < localMessages.length; i++) {
        messages.push(localMessages[i])
    }
}

These both look like they are doing almost the same thing. once you get the functions running well, post a follow up question and we can take a look at them and see if they are efficient.

\$\endgroup\$
  • \$\begingroup\$ Thanks a ton! This is great. I'm going to polish up what I have now and post again like you suggest. One think I've been trying to figure out is that I want it to notice if you say both 'BET' and 'CHECK' but what it has right now is just picking one to be arbitrarily based on how high up the chain it is. I tried making an object with the trigger and its effect that could be looped, but I never got that to work. \$\endgroup\$ – Spencer Fleming Sep 14 '14 at 0:07
  • \$\begingroup\$ Also, not to complain or anything, but I'm kinda curious, how did you find this three month old post? \$\endgroup\$ – Spencer Fleming Sep 14 '14 at 0:36
  • 1
    \$\begingroup\$ @user31157 - it's a zombie \$\endgroup\$ – rolfl Sep 15 '14 at 0:36
  • \$\begingroup\$ @user31157, glad to have you aboard the Code Review Question Askers! \$\endgroup\$ – Malachi Sep 15 '14 at 13:19
  • 1
    \$\begingroup\$ It's good to put bad code in the OP inside blockquote, to distinguish the mess from your own suggestions \$\endgroup\$ – Stop ongoing harm to Monica Sep 30 '14 at 15:57

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.