Seven Languages In Seven Weeks: Scala - Day 1

After just barely surviving Prolog, it's time for the Scala chapter in my Seven Langauges in Seven Weeks book by Bruce Tate. Coming from a ColdFusion background, it's comforting to know that Scala runs on the JVM (Java Virtual Machine) in the same way that ColdFusion does; and, just as with ColdFusion, Scala has access to all of the core Java libraries that I typically use for fun and profit.

Unlike Java, however, Scala is not so ceremonious. You don't need semi-colons (the horror!). You also don't need to define data types; as long as you initialize your variables at the time of declaration, the compiler can generally infer the data type from the initial value. The use of parenthesis is also somewhat optional; although, they seem to follow a ruleset that I don't quite grasp yet. There's also what looks like Ruby and Io style code blocks that can act as inline functions. Oh, and return statements are optional - the last executed statement of any function is the implied return value. Of course, you have to be careful because it seems that all operations in Scala return a value, even if that value is "Unit" (which can generally be thought of as "void").

Because so much of the language is optional, there was a lot of compiling the code and letting the the compiler tell me where I need to make things more explicit. For example, you don't need to define return types for functions; but, if you use a return() statement, you do need to define a return type. This homework took me about 4 hours and involved a lot trial and error.

Also, special thanks to Sean Corfield who helped me figure out why the heck I was getting "Unit" and "Boolean" mismatched types. The default return type for a function is "Unit" (think "void"). However, if you don't use a return statement, the compiler will infer the appropriate return type based on the code within the given function. Seeing as I am in love with the return statement, however, the Scala compiler was not able to determine my return type; as such, my functions that returned Booleans were being seen as returning Unit. As Sean pointed out, either adding the Boolean return type OR removing the return statement would fix this error (although Sean also pointed out that removing the the return statements was more "proper" Scala).

HW1: Write a game that will take a tic-tac-toe board with X, O, and blank characters and detect the winner or whether there is a tie or no winner yet. Use classes where appropriate. BONUS: Let two players play tic-tac-toe.

I'm sure I went a little overboard on this one; but, the more complex I made the solution, the more opportunity I'd figure I'd have to get comfortable with the constructs of the Scala programming language. The way I saw it, a Tic Tac Toe game needed a few different classes:

TicTacToeCell - This represented a single cell on a Tic Tac Toe board. It managed the state of the given cell and acted as a gatekeeper to setting the occupant value.

TicTacToeBoard - This represented a Tic Tac Toe board which composed a list of 9 TicTacToeCell instances. The board was capable of determining if there were any possible moves left to be made, if there was a tie or a winner, and if there was a winner, who that winner was. The board itself makes no assumptions about game rules or who is playing.

TicTacToeGame - This was the class that was actually ran the Tic Tac Toe game on a given board. This was the class that worried about alternating players, populating the board, and stopping once a winner emerged or a board became exhausted.

// Write a game that will take a tic-tac-toe board with X, O, and // blank characters and detect the winner or whether there is a tie // or on winner yet. Use classes where appropriate. // // BONUS: Let two players play tic-tac-toe. // I represent a single cell within a TicTacToe board. class TicTacToeCell { // I am the occupant of the cell. By default, there is no // occupant of the cell, which is indicated by a blank // character. // // NOTE: I tried to start out with Nil, but kept getting type // conversion errors when setting it later on. var occupant = " "; // I am the list of valid occupants. val validOccupants = List( "X", "O" ); // I compare the current cell to an incoming cell. Two cells // are said to be equal if their occupants are the same. def ==( target: TicTacToeCell ):Boolean = { return( this.getOccupant() == target.getOccupant() ); } // I return the current cell occupant. def getOccupant():String = this.occupant; // I determine if the current cell is occupied. def isOccupied() = { (this.getOccupant() != " "); } // I determine if the given occupant value would be valid. within // this cell. def isValidOccupant( occupant: String ) = { this.validOccupants.contains( occupant ); } // I set the given occupant. def setOccupant( newOccupant: String ):Unit = { // Check to make sure the current cell is not occupied. if (this.isOccupied()){ // This cell can only be occupied once. throw new IllegalStateException( "This cell is currently occupied by " + this.occupant + ". A cell can only be occupied once." ); } // Check to make sure that the incoming occupant is valud. if (!this.isValidOccupant( newOccupant )){ // The incoming occupant needs to be valid. throw new IllegalArgumentException( "The incoming occupant is not valid. Valid types include: " + this.validOccupants ); } // If we made it this far, then the cell can accept the // incoming occupant. Store it. this.occupant = newOccupant; } // Override the toString() method to allow the cell to be // converted to a string reprentation as a function of it // current occupant. override def toString():String = { // Check to see if the cell is occupied - this will determine // how we want to represent the cell. if (this.isOccupied()){ // Returnt the occupant. return( this.getOccupant() ); } else { // Return a dot. This is simply so that I can copy/paste // this on my b.blog without losing multiple empty spaces // in a row. return( "." ); } } } // ---------------------------------------------------------- // // ---------------------------------------------------------- // // I represent a TicTacToe board. class TicTacToeBoard { // Our board is just going to be a list. The indices of the // board will go from 0 to 8; 0 being the top-left and 8 // being the bottom-right. val board = List( new TicTacToeCell(), new TicTacToeCell(), new TicTacToeCell(), new TicTacToeCell(), new TicTacToeCell(), new TicTacToeCell(), new TicTacToeCell(), new TicTacToeCell(), new TicTacToeCell() ); // Define all the paths that can contain a winner. A winner // is three in a row in any of the horizontal, vertical, or // diagonal paths. val winningPaths = List( List( 0, 1, 2 ), List( 3, 4, 5 ), List( 6, 7, 8 ), List( 0, 3, 6 ), List( 1, 4, 7 ), List( 2, 5, 8 ), List( 0, 4, 8 ), List( 2, 4, 6 ) ); // I get the winner of the board. def getWinner():String = { // Check to make sure there is a winner before we try and // find it on the board. if (!this.hasWinner){ // There has to be a winner before one can be found. throw new IllegalStateException( "The board does not have a winner." ); } // Loop over the paths to check each one against the board. // Once we find the winning path, we'll know which occupant // was the winner. this.winningPaths.foreach( path => // Check to see if this path contains a winner. if (this.pathContainsWinner( path )){ // Return the first occupant of this winning path. return( this.board( path( 0 ) ).getOccupant() ); } ); // We'll never make it this far, but the compiler doesn't // know this. As such, it appears that I need a return // statement here to not get a type mismatch on return type. return( "SHOULD NEVER MAKE IT THIS FAR" ); } // I determine if there are any empty cells on the baord. def hasFreeCells():Boolean = { // Iterate over the cells to determine if any of them have // yet to be occupied. this.board.foreach( cell => // Check to see if the current cell is occupied. if (!cell.isOccupied()){ // Since the cell is NOT occupied, then we can // short-circuit our testing and return - no need // to check any more cells. return( true ); } ); // If we made it this far then we could not find any empty // cells. return( false ); } // I determine if the board has more moves. This will true if // there are empty cells AND there is no winner yet. def hasNextMove() = { (this.hasFreeCells() && !this.hasWinner); }; // I determine if the board has a winner. def hasWinner:Boolean = { // Loop over the paths to check each one against the board. this.winningPaths.foreach( path => // Check to see if this path contains a winner. if (this.pathContainsWinner( path )){ // The board has a winner. return( true ); } ); // If we made it this far then none of the possible WIN paths // contain winners. return( false ); } // I determine if board is a tie - this is basically the case if // there are no more free cells AND there is NO winner. def isTieBoard() = { (!this.hasFreeCells() && !this.hasWinner); } // I determine if the given path contains a winner. def pathContainsWinner( path: List[Int] ) = { // Get the cells at the given path indices. val cell1 = this.board( path( 0 ) ); val cell2 = this.board( path( 1 ) ); val cell3 = this.board( path( 2 ) ); // Determine if the given cells constitue a winner. cell1.isOccupied() && (cell1 == cell2) && (cell1 == cell3); } // I place the given occupant in the given cell. This method // with throw an error if the index is either out of bounds or // the given cell is already occupied. def setOccupant( cellIndex: Int, occupant: String ) = { this.board( cellIndex ).setOccupant( occupant ); }; // I override the toString() method to allow for pretty printing // of the board. This assumes that each Cell can be implicitly // turned into a string using the implied toString() method. override def toString():String = { return( this.board( 0 ) + " | " + this.board( 1 ) + " | " + this.board( 2 ) + "

" + "---------

" + this.board( 3 ) + " | " + this.board( 4 ) + " | " + this.board( 5 ) + "

" + "---------

" + this.board( 6 ) + " | " + this.board( 7 ) + " | " + this.board( 8 ) + "

" ); }; } // ---------------------------------------------------------- // // ---------------------------------------------------------- // // I play a tic tac toe game using the tic-tac-toe board. class TicTacToeGame { // Start the game play!! It's GO TIME! // --------------------------- // this.play(); // --------------------------- // // I return the next player for the given player. Basically, // I know how to toggle between X and O. def getNextPlayer( player: String ):String = { // Check to see what the current player is. if (player == "X"){ // Toggle to O. return( "O" ); } else { // Toggle to X. return( "X" ); } } // I play the game. def play():Unit = { // Create a new board to play. var board = new TicTacToeBoard(); // Keep track of which player is current. var currentPlayer = "X"; // Print a new line for readability. println( "" ); // Start playing and keep playing while the board has a next // available move on it. Once we are out of movies, we can // check to see if there is a tie or if there is a winner. while( board.hasNextMove() ){ // Print the current board. println( board ); // Print the current player's move. print( "

" + currentPlayer + "'s Move: " ); // Get the index value and set the occupant. This will // raise an exception if the index is out of bounds OR // if the given cell is already occupied. try { // Get the current player's chosen index from the // console prompt. var cellIndex = Console.readInt(); // Populate the cell with the given player. board.setOccupant( cellIndex, currentPlayer ); // Switch the player so the next person can take // their turn. currentPlayer = this.getNextPlayer( currentPlayer ); } catch { // The selected index is not valid. case e:Exception => println( "That index is not valid!" ); } // Print a new line before the board is output. println( "" ); } // If we've made it this far then there are not more moves // on the given board. Now, we need to check to see if this // is because it was a tie or because someone actually won. // Print out the final state of the board. println( board ); // If we have gotten this far then there are no more moves. // Either someone has won or there is tie. if (board.isTieBoard()){ // Output the tie game message. println( "Tie Game!" ); } else { // Get the winner. var winner = board.getWinner(); // Output the winner message. println( "And the winner is: " + winner ); println( "Hate the game, not the player!" ); } // Now, check to see if the players want to go at it again. print( "

Want to play again? (Y/N) " ); // Get the user's response. var playAgain = Console.readLine().toLowerCase(); // Anything but an "n" or empty space will play again. if ((playAgain != "") && (playAgain != "n")){ // Play again! this.play(); } } } // ---------------------------------------------------------- // // ---------------------------------------------------------- // // ---------------------------------------------------------- // // ---------------------------------------------------------- // // Create a new game (this will start playing automatically). var game = new TicTacToeGame();

As you can see, a Scala code file can contain both class definitions and procedural code. In the above file, I am defining my three classes before I instantiate a TicTacToeGame class and kick off the game play. Doing so resulted in the following console output:

. | . | .

---------

. | . | .

---------

. | . | . X's Move: 0 X | . | .

---------

. | . | .

---------

. | . | . O's Move: 1 X | O | .

---------

. | . | .

---------

. | . | . X's Move: 2 X | O | X

---------

. | . | .

---------

. | . | . O's Move: 3 X | O | X

---------

O | . | .

---------

. | . | . X's Move: 4 X | O | X

---------

O | X | .

---------

. | . | . O's Move: 5 X | O | X

---------

O | X | O

---------

. | . | . X's Move: 6 X | O | X

---------

O | X | O

---------

X | . | . And the winner is: X

Hate the game, not the player! Want to play again? (Y/N) N

Choosing the moves in the game is kind of a pain in the butt - the user has to pick the index of the cell within the list of composed cells. That means the user has to not only understand the data structure, she has to be able to mentally map the 3x3 grid onto a 0-8 list. Yes, very lame.

After Prolog, being back in a "traditional" programming language feels like sweet relief. Scala definitely has its own set of quirks (ex. a recursive function needs to have a declared return type), but once you get past some of those peculiarities, the language is fun to work with. So far, I haven't demonstrated anything in Scala that makes the language all that special; according to Tate, though, the language will really start to shine when we see how well it integrates Object-Oriented Programming with Functional Programming.

Tweet This Great article by @BenNadel - Seven Languages In Seven Weeks: Scala - Day 1 Woot woot — you rock the party that rocks the body!







