require 'Qt' class TicTacButton < Qt::PushButton attr_accessor :btype Blank, Circle, Cross = 0, 1, 2 def initialize(p) super(p) @btype = Blank end def drawButtonLabel(p) r = rect() p.setPen( Qt::Pen.new( Qt::white,2 ) ) # set fat pen if (@btype == Circle) p.drawEllipse( r.left()+4, r.top()+4, r.width()-8, r.height()-8 ) elsif (@btype == Cross) # draw cross p.drawLine( r.topLeft() +Qt::Point.new(4,4), r.bottomRight()-Qt::Point.new(4,4)) p.drawLine( r.bottomLeft()+Qt::Point.new(4,-4),r.topRight() -Qt::Point.new(4,-4)) end super(p) end end class TicTacGameBoard < Qt::Widget signals 'finished()' slots 'buttonClicked()' Init, HumansTurn, HumanWon, ComputerWon, NobodyWon = 0, 1, 2, 3, 4 attr_accessor :state, :computer_starts def initialize (n, parent) super(parent) @state = Init @nBoard = n n = n*n @computer_starts = false @buttons = Array.new(n) @btArray = Array.new(n) grid = Qt::GridLayout.new(self, n, n, 4) p = Qt::Palette.new(Qt::blue) for i in (0..n-1) ttb = TicTacButton.new(self) ttb.setPalette(p) ttb.setEnabled(false) connect(ttb, SIGNAL('clicked()'), self, SLOT('buttonClicked()')) grid.addWidget(ttb, i % @nBoard, i / @nBoard) @buttons[i] = ttb @btArray[i] = TicTacButton::Blank end end def newGame @state = HumansTurn for i in 0..(@nBoard*@nBoard)-1 @btArray[i] = TicTacButton::Blank end if @computer_starts == true computerMove else updateButtons end end def updateButtons for i in 0..(@nBoard*@nBoard)-1 if @buttons[i].btype != @btArray[i] @buttons[i].btype = @btArray[i] end if @buttons[i].btype == TicTacButton::Blank @buttons[i].setEnabled(true) else @buttons[i].setEnabled(false) end @buttons[i].repaint end end def checkBoard t = 0 row = 0 col = 0 won = false # check horizontal for row in 0..@nBoard-1 if won == true break end t = @btArray[row*@nBoard] if (t == TicTacButton::Blank) next end col = 1 while ( (col < @nBoard) && (@btArray[row*@nBoard+col] == t) ) col += 1 end if (col == @nBoard) won = true end end # check vertical for col in 0..@nBoard-1 if won == true break end t = @btArray[col] if (t == TicTacButton::Blank) next end row = 1 while ( (row < @nBoard) && (@btArray[row*@nBoard+col] == t) ) row += 1 end if (row == @nBoard) won = true end end # check diagonal top left to bottom right if (won == false) t = @btArray[0] if (t != TicTacButton::Blank) i = 1; while (i<@nBoard && (@btArray[i*@nBoard+i] == t)) i += 1 end if (i == @nBoard) won = true end end end # check diagonal bottom left to top right if (won == false) j = @nBoard-1 i = 0; t = @btArray[i+j*@nBoard]; if (t != TicTacButton::Blank) i += 1 j -= 1 while ( (i<@nBoard) && (@btArray[i+j*@nBoard] == t) ) i += 1 j -= 1 end if (i == @nBoard) won = true end end end if (won == false) # no winner t = 0 end t end def computerMove numButtons = @nBoard*@nBoard altv = Array.new stopHuman = -1 i = 0 for i in 0..numButtons-1 # try all positions if @btArray[i] != TicTacButton::Blank # already a piece there next end @btArray[i] = TicTacButton::Cross # test if computer wins if (checkBoard == @btArray[i]) # computer will win @state = ComputerWon stopHuman = -1 break end @btArray[i] = TicTacButton::Circle # test if human wins if (checkBoard == @btArray[i]) # oops... stopHuman = i # remember position @btArray[i] = TicTacButton::Blank # restore button next # computer still might win end @btArray[i] = TicTacButton::Blank; # restore button altv.push(i) # remember alternative end if (stopHuman >= 0) # must stop human from winning @btArray[stopHuman] = TicTacButton::Cross elsif (i == numButtons-1) # tried all alternatives if (altv.size > 0) # set random piece @btArray[altv[rand(altv.size)]] = TicTacButton::Cross end if ((altv.size-1) == 0) # no more blanks @state = NobodyWon emit finished() end end updateButtons # update buttons end def buttonClicked unless @state == HumansTurn return end at = nil for i in 0..@buttons.size if @buttons[i].object_id == sender.object_id at = i break end end if @btArray[at] == TicTacButton::Blank @btArray[at] = TicTacButton::Circle updateButtons if (checkBoard == 0) computerMove end s = checkBoard if (s != 0) if (s == TicTacButton::Circle) @state = HumanWon else @state = ComputerWon end emit finished() end end end end class TicTacToe < Qt::Widget slots 'newGameClicked()', 'gameOver()' def initialize (boardSize) super() l = Qt::VBoxLayout.new(self, 6) @state_msg = [ 'Click Play to start', 'Make your move', 'You won!', 'Computer won!', 'It\'s a draw'] # Create a message label @message = Qt::Label.new(self) @message.setFrameStyle((Qt::Frame.WinPanel|Qt::Frame.Sunken)) @message.setAlignment(Qt::AlignCenter) l.addWidget(@message) # Create the game board and connect the signal finished() # to this/self gameOver() slot @board = TicTacGameBoard.new(boardSize, self) connect(@board, SIGNAL('finished()'), self, SLOT('gameOver()')); l.addWidget(@board) # Create a horizontal frame line line = Qt::Frame.new(self) line.setFrameStyle(Qt::Frame.HLine|Qt::Frame.Sunken) l.addWidget(line) # Create the combo box for deciding who should start # and connect its clicked() signals to the buttonClicked() slot @whoStarts = Qt::ComboBox.new(self) @whoStarts.insertItem('Computer starts') @whoStarts.insertItem('Human starts') l.addWidget(@whoStarts); # Create the push buttons and connect their signals to the right slots @newGame = Qt::PushButton.new('Play!', self) connect(@newGame, SIGNAL('clicked()'), self, SLOT('newGameClicked()')) @quit = Qt::PushButton.new('Quit', self) connect(@quit, SIGNAL('clicked()'), $qApp, SLOT('quit()')) b = Qt::HBoxLayout.new l.addLayout(b) b.addWidget(@newGame) b.addWidget(@quit) newState() end def newState @message.setText(@state_msg[@board.state]) end def newGameClicked if @whoStarts.currentItem == 0 @board.computer_starts = true else @board.computer_starts = false end @board.newGame() newState() end def gameOver # Update text box newState() end end