Toppling Dominoes Computer Tournament!

Round 0

Standings Summary


0 - 0
  • Random #0
  • Random #1
  • Depth One #0
  • Depth One #1
  • MCTS #0
  • Depth Two #0
  • Depth Two #1

Matches


Full Standings

  1. Depth One #1: (0 - 0, opp matches: 0 - 0, games: 0 - 0, opp games: 0 - 0)
    • Random #1: (0 - 0, opp matches: 0 - 0, games: 0 - 0, opp games: 0 - 0)
      • Depth Two #0: (0 - 0, opp matches: 0 - 0, games: 0 - 0, opp games: 0 - 0)
        • MCTS #0: (0 - 0, opp matches: 0 - 0, games: 0 - 0, opp games: 0 - 0)
          • Random #0: (0 - 0, opp matches: 0 - 0, games: 0 - 0, opp games: 0 - 0)
            • Depth One #0: (0 - 0, opp matches: 0 - 0, games: 0 - 0, opp games: 0 - 0)
              • Depth Two #1: (0 - 0, opp matches: 0 - 0, games: 0 - 0, opp games: 0 - 0)

                Make-Your-Own Player

                (We recommend that you write your code in a separate text editor, then copy-paste it here.)


                (This does not submit your player to the tournament. There are further directions below for that.)

                Here is an example that finds the first domino of the correct color and knocks it to the right:

                function userGetMove(playerId, position) {
                    for (var i = 0; i < position.rows.length; i++) {
                    const row = position.rows[i];
                    for (var j = 0; j < row.length; j++) {
                        const dominoColor = row[j];
                        if (dominoColor == playerId) {
                            //this domino is my color
                            //knock it to the right
                            row.splice(j, row.length - j + 1);
                            return position;
                            }
                        }
                    }
                }

                EFAQ: Expected Frequently-Asked Questions

                What is Toppling Dominoes?

                Toppling Dominoes is a partizan combinatorial game played on rows of blue and red dominoes. On their turn, the current player (Blue or Red) chooses one of their dominoes in any row. They then choose to tip it to the right or left. The domino knocks over all other dominoes in its path and all of them are removed from the game. You can play Toppling Dominoes here.

                What is this for?

                We are holding a computer player tournament as part of Sprouts 2025. People can use this to test their players. Instructions to submit a player are below.

                Can I work on a team, or do I have to work independently?

                You can work in a group or on your own.

                How big will the boards be in the tournament? How many games will be played per match?

                We are planning to move forward with boards with 8 rows, each with a maximum of 10 dominoes per row. I am planning 15 games per match.

                I wrote a brute-force player that always finds the optimal solution. Do I win?

                We'll need players to run efficiently. For the actual conference tournament, if we run this with a bunch of contestants at the same time as we're running Zoom, it will get bogged down quickly. (And Kyle's laptop is not very powerful.) Please make sure your player takes their turn in less than 6 seconds on our starting positions on your own machine. (If your machine isn't too overpowered, that should equate to about 15 seconds on my laptop.) If specific players are running too long, we'll have to exclude them from the tournament. (If you disagree with these rules, feel free to talk to me. We would much rather have more players than fewer!)

                What if I want to add more details to my player than just what current move to make? Does it have to be stateless?

                Below, you'll see the details for submitting your player class. It does not have to be "stateless"; you can definitely include fields that your player uses to make moves.

                I got a player working real well! How do I submit it to your tournament?

                Check out the instructions below. (After this EFAQ.)

                I actually have two players and I would like to test them out against each other. Can I do that?

                Yes, those directions are below!

                How do I get updates about the tournament?

                Keep watching this space, or watch @CGTKyle@mathstodon.xyz (Mastodon) for updates.

                Kyle, this code is awful! Did you write this? I see tons of scripting code in the HTML source and inline styling. What have you done?

                Oh yeah. I got it working, but I definitely need to clean it up. Please don't tell my software engineering students! I'll refactor it when I have time (please don't check to see if this answer is the same as it was last year).


                Submitting Your Player to the Actual Tournament

                If you get a player working as above, you'll need to make a few changes to get it working for the actual Sprouts tournament.

                1. Choose a name for your player. The name should (1) be something no one else will choose, (2) be in PascalCase, and (3) be appropriate to read and speak in an academic setting. We reserve the right to drop players without notice, and vulgar or inappropriate language is part of that. We recommend keeping the name length to 15 characters.
                2. Your class will need to subclass the ComputerPlayer class I've created, so you'll need to add some extra code to wrap your function in. Copy/paste the following code into a text file named <YourPlayerName>.js, with <YourPlayerName> replaced by the name you chose above.
                  //author: <Your name or your team's name>, yourcontactemail@example.com
                  var <YourPlayerName> = Class.create(ComputerPlayer, {
                      initialize: function() {
                          //nothing needed here, but you can add things
                          //if you want a stateful player
                      },
                  
                      givePosition: function(playerIndex, position, referee) {
                          //don't modify this method.
                          referee.moveTo(this.userGetMove(playerIndex, position));
                      },
                  
                      userGetMove: function(playerId, position) {
                          //paste your code in here.
                      },
                  
                      getName: function() {
                          return "<YourPlayerName>";
                      },
                  
                      getAuthor: function() {
                          return "<Your name or your team's name>";
                      }
                  });
                3. Copy paste the body of your userGetMove function into the function of the same name there. Feel free to add to your player's initialize (constructor) if you need to hold any info between moves.
                4. Modify the name of the class to be your player's name. Modify the body of getName to also return your player's name as a string. Modify the body of getAuthor to return your name or your team's name. Finally, modify the first line to include your team name and a contact email address so we can get in touch with you if necessary.
                5. If you make significant modifications, ensure again that your player makes moves within 6 seconds on the suggested board sizes.
                6. Use our Dropbox to submit your code. By submitting your player to us, you are giving us permission to run the code in a public setting. As mentioned above, we reserve the right to not include your submission in the tournament. Please use this Dropbox link to submit your player.
                7. In order to be sure to get your player included in the tournament, please submit by 11:59pm (Eastern US Time) Thursday, April 10, 2024. The link above will continue to work after that time but if you miss the deadline, please reach out to me via email (paithanq@gmail.com) so we'll be notified of your late submission. If we are able to, we'll include your player! (Most years so far we are low on players and would be excited to get more.)
                8. If you create an even better player, please resubmit your code with the same (file) name. If this system works, we'll use the latest version provided before the deadline above.

                Testing Two Players

                By popular demand, we've included a way to pit two (or more) players against each other.


                Toppling Dominoes code

                Here is the underlying JavaScript code for the Toppling Dominoes class, which uses the prototype package to define objects. It is currently a part of the (very large) combinatorialGames.js file I maintain.

                /**
                 * Toppling Dominoes game
                 * 
                 * Grid is stored as a 2D array of single rows.  Each row is an array of integers where each represents the color of one domino.  In order to keep things looking neat, we leave in the space where fallen dominoes were; thus we also include empty dominoes in the arrays, which are unplayable. 
                 * @author Kyle Burke.  
                 */
                const TopplingDominoes = Class.create(CombinatorialGame, {
                    
                    /**
                     * Constructor.
                     * 
                     */
                    initialize: function(numRows, minDominoesInRow, maxDominoesInRow) {
                        this.playerNames = ["Blue", "Red"];
                        this.rows = [];
                        //default probability
                        const colors = [CombinatorialGame.prototype.LEFT, CombinatorialGame.prototype.RIGHT];
                        var numBlueEnds = 0;
                        var numRedEnds = 0;
                        for (var i = 0; i < numRows; i++) {
                            const row = [];
                            const dominoDifference = maxDominoesInRow - minDominoesInRow;
                            const numDominoes = Math.floor(Math.random() * dominoDifference) + minDominoesInRow;
                            for (var j = 0; j < numDominoes; j++) {
                                row.push(randomChoice(colors));
                            }
                            this.rows.push(row);
                            if (row[0] == CombinatorialGame.prototype.LEFT) {
                                numBlueEnds ++;
                                if (numBlueEnds > numRows) {
                                    //we have too many blue dominoes on the ends and need to make this one red instead
                                    row[0] = CombinatorialGame.prototype.RIGHT;
                                    numRedEnds++;
                                    numBlueEnds--;
                                } 
                            } else {
                                numRedEnds ++;
                                if (numRedEnds > numRows) {
                                    //we have too many red dominoes on the ends and need to make this one blue instead
                                    row[0] = CombinatorialGame.prototype.LEFT;
                                    numRedEnds--;
                                    numBlueEnds++;
                                }
                            }
                            if (row[row.length-1] == CombinatorialGame.prototype.LEFT) {
                                numBlueEnds++;
                                if (numBlueEnds > numRows) {
                                    //we have too many blue dominoes on the ends and need to make this one red instead
                                    row[row.length-1] = CombinatorialGame.prototype.RIGHT;
                                    numRedEnds++;
                                    numBlueEnds--;
                                } 
                            } else {
                                numRedEnds++;
                                if (numRedEnds > numRows) {
                                    //we have too many red dominoes on the ends and need to make this one blue instead
                                    row[row.length-1] = CombinatorialGame.prototype.LEFT;
                                    numRedEnds--;
                                    numBlueEnds++;
                                }
                            }
                        }
                    }
                    
                    /**
                     * Returns the width of this board.
                     */
                    ,getMaxNumDominoes: function() {
                        var maxLength = 0;
                        for (const row of this.rows) {
                            if (row.length > maxLength) {
                                maxLength = row.length;
                            }
                        }
                        return maxLength;
                    }
                    
                    /**
                     * Returns the height of this board.
                     */
                    ,getNumRows: function() {
                        return this.rows.length;
                    }
                    
                    /**
                     * Equals!
                     */
                    ,equals: function(other) {
                        return omniEquals(this.rows, other.rows);
                    }
                    
                    /**
                     * Clone.
                     */
                    ,clone: function() {
                        const copy = new TopplingDominoes(1, 1, 1);
                        copy.rows = omniClone(this.rows);
                        return copy;
                    }
                    
                    /**
                     * Gets the options.
                     */
                    ,getOptionsForPlayer: function(playerId) {
                        const options = [];
                        for (var i = 0; i < this.rows.length; i++) {
                            const row = this.rows[i];
                            for (var j = 0; j < row.length; j++) {
                                if (row[j] == playerId) {
                                    options.push(this.getLeftPushOption(i, j));
                                    options.push(this.getRightPushOption(i, j));
                                }
                            }
                        }
                        return options;
                    }
                    
                    /**
                     * Gets an option by knocking a domino to the left.
                     */
                    ,getLeftPushOption: function(rowIndex, dominoIndex) {
                        const option = this.clone();
                        //option.rows[rowIndex].splice(0, dominoIndex+1);
                        for (var i = 0; i <= dominoIndex; i++) {
                            option.rows[rowIndex][i] = TopplingDominoes.prototype.NO_DOMINO;
                        }
                        return option;
                    }
                    
                    /**
                     * Gets an option by knocking a domino to the right.
                     */
                    ,getRightPushOption: function(rowIndex, dominoIndex) {
                        const option = this.clone();
                        //option.rows[rowIndex].splice(dominoIndex, option.rows[rowIndex].length - dominoIndex);
                        for (var i = dominoIndex; i < option.rows[rowIndex].length; i++) {
                            option.rows[rowIndex][i] = TopplingDominoes.prototype.NO_DOMINO;
                        }
                        return option;
                    }
                    
                }); // end of TopplingDominoes
                TopplingDominoes.prototype.PLAYER_NAMES = ["Blue", "Red"];