2009-03-31 16:52Groovy Tic-Tac-ToeI have enjoyed using Groovy in the past, and even think it might be the nicest (programming) language in the world, so I thought I would try using it for a fun little project. Perhaps I should have remembered how difficult it was writing a graphical program in Java (on which Groovy is based), or perhaps I should have given up when I realised that Debian doesn’t have a nice Groovy 2D graphics library packaged yet, but my hubris got the better of me and I waded in, trying to create the game Tic-Tac-Toe. I would call it “Noughts and Crosses”, except the limitations of the graphical commands available meant I couldn’t do crosses. So, I will show you the source code needed to run this game, and warn you of the difficulties you will encounter if you try my bizarre way of doing graphics in Groovy. The Codeimport java.awt.* import javax.swing.* import groovy.swing.SwingBuilder // Make some variables global to be reused by functions swing = new SwingBuilder() player = 0 gameOver = false wins = [[0,1,2],[3,4,5],[6,7,8],[0,3,6],[1,4,7],[2,5,8],[0,4,8],[2,4,6]] grid = [0]*9 def checkWinner() { return wins.collect{grid.getAt(it)}.collect{it.unique().size == 1 && it[0] != 0}.inject(false){total, item -> total || item} } def cellClickAction(e, panels) { if (gameOver) return; cellId = e.source.name.toInteger() if (grid[cellId]) { return false } else { grid[cellId] = (player%2)+1 } def panel = panels[cellId] def color = (player%2) ? Color.RED : Color.BLUE def newBorder // Need a swing context to create a lineBorder, which is then assigned to a variable defined outside of the context swing.panel() { newBorder = lineBorder(color:color, roundedCorners:true, thickness:50) } panel.border = newBorder gameOver = checkWinner() return true } def changePlayer(textlabel) { if (!gameOver) player++ def playerName = (player%2) ? "Red" : "Blue" if (gameOver) { textlabel.text = playerName + " wins!" } else { textlabel.text = playerName + "'s go." } } def resetBoard(panels) { def newBorder for (panel in panels.findAll{it != null}) { // Need a swing context to create a lineBorder, which is then assigned to a variable defined outside of the context swing.panel() { newBorder = lineBorder(color:Color.WHITE, roundedCorners:true, thickness:50) } panel.border = newBorder } grid = [0]*9 // Reset the board state } def frameMaker() { def legend = [0:Color.WHITE, 1:Color.RED, 2:Color.BLUE] def frame = swing.frame(title:'TicTacToe', preferredSize:[303,303]) { // This closure is an anonymous inner class which inherits from the superclass created by swing.frame // Moving this closure outside of its current context would make functions like gridLayout undefined boxLayout(axis:BoxLayout.Y_AXIS) def textlabel textlabel = label(text:"Blue's go.", constraints: BorderLayout.WEST) def panels = [] def currentPanel panel(constraints: BorderLayout.CENTER) { gridLayout(columns:3, rows:3, vgap:1, hgap:1) for (i in 0..8) // 9 cells { color = legend[i] currentPanel = panel( preferredSize:[4,4], name:i, background:Color.WHITE, border:lineBorder(color:Color.WHITE, roundedCorners:true, thickness: 50), mouseClicked:{e -> if (cellClickAction(e, panels)) { changePlayer(textlabel) } } ) panels[i] = currentPanel } } button(text:'Reset', actionPerformed: { gameOver = false; resetBoard(panels); changePlayer(textlabel) }, constraints:BorderLayout.SOUTH) } } frame = frameMaker() frame.pack() frame.show() CommentsOf course, good code should be self-documenting, but I accept this is ugly, ugly code, and it never hurts to give people a bit of an introduction to understanding a complex program. Starting at the bottom, then, there are three fairly trivial lines which create a frame from the Looking inside Aside from these constraints, the rest of the code is relatively straightforward. Nine panels are created, numbered 0 to 8, and are given that number as their I’ve always liked programming one-liners, and the Assuming you have some experience of closures, then, I will try to explain how this one-line closure monster works. Firstly I need to point out the wins variable which is an array of triples, and these triples each contain the indices of three panels on the board which are in a straight line. The question that the function has to answer, then, is “Do any of these triples refer to three panels that are all red or all blue?”, since if they do then red or blue has created a line and has won. As the code stores whose go it is, the function doesn’t need to determine who the winner is itself, as it should be the player who has just played. The flow of logic is from left to right in this line, so what happens is that wins is considered, and for each of its triples a new triple is formed containing the values of the colours on the grid at the three indices (zero for white, one for red or two for blue). Each of these triples is then condensed down into an array of unique values, such that a triple representing three panels of the same colour gets condensed into a singleton of that colour. The size of this condensed array then gets checked to see if it is a singleton, and the first value of the array is checked to make sure it isn’t zero (representing white). If these checks both pass, the condensed triple is replaced by ConclusionSo, I should point out that I don’t deliberately try to make life hard for myself, but if you don’t push yourself you don’t know what you’re capable of. If I had gone for a text-based interface, I could have had more control and structured the code more how I wanted, but then I might as well have written it in PHP or JavaScript, as I wouldn’t be demonstrating the powerful desktop-app capabilities of Groovy. Having said that, other than starting in a well-sized chromeless window, there is not much difference from loading this program implemented as a DHTML page and loading it as a Groovy script, except the user would need Groovy installed on their machine if they wanted to run the code above. All that remains is for me to point out what is wrong with the program still, apart from the choice of language and the ugly programming. The main thing is that the text label and the reset button are centred based on their left hand edge, unlike the conventional form of centring where the centre of an object is aligned to the centre of its container. I don’t know why this is, and I leave it as an opportunity for commenters on my blog to provide a patch to show how clever they are. This might actually be easy, because I didn’t spend much time trying to look for a solution after spending ages trying to solve an earlier graphical problem… Why is it so hard to set the background colour so that the thin grey lines between the white panels are black instead? |
QuicksearchCategoriesSyndicate This BlogBlog Administration |
I also had a few points I wanted to make about other aspects of the language, including things I found while working on the game that I presented in my last blog post, so I thought that this would be a good time to write up these ideas.
Tracked: Jun 06, 16:51