You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
312 lines
6.5 KiB
312 lines
6.5 KiB
require 'Qt'
|
|
|
|
class TicTacButton < TQt::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( TQt::Pen.new( TQt::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() +TQt::Point.new(4,4), r.bottomRight()-TQt::Point.new(4,4))
|
|
p.drawLine( r.bottomLeft()+TQt::Point.new(4,-4),r.topRight() -TQt::Point.new(4,-4))
|
|
end
|
|
super(p)
|
|
end
|
|
end
|
|
|
|
class TicTacGameBoard < TQt::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 = TQt::GridLayout.new(self, n, n, 4)
|
|
p = TQt::Palette.new(TQt::blue)
|
|
|
|
for i in (0..n-1)
|
|
ttb = TicTacButton.new(self)
|
|
ttb.setPalette(p)
|
|
ttb.setEnabled(false)
|
|
connect(ttb, TQ_SIGNAL('clicked()'), self, TQ_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 < TQt::Widget
|
|
slots 'newGameClicked()', 'gameOver()'
|
|
|
|
def initialize (boardSize)
|
|
super()
|
|
|
|
l = TQt::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 = TQt::Label.new(self)
|
|
@message.setFrameStyle((TQt::Frame.WinPanel|TQt::Frame.Sunken))
|
|
@message.setAlignment(TQt::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, TQ_SIGNAL('finished()'), self, TQ_SLOT('gameOver()'));
|
|
l.addWidget(@board)
|
|
|
|
# Create a horizontal frame line
|
|
line = TQt::Frame.new(self)
|
|
line.setFrameStyle(TQt::Frame.HLine|TQt::Frame.Sunken)
|
|
l.addWidget(line)
|
|
|
|
# Create the combo box for deciding who should start
|
|
# and connect its clicked() signals to the buttonClicked() slot
|
|
@whoStarts = TQt::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 = TQt::PushButton.new('Play!', self)
|
|
connect(@newGame, TQ_SIGNAL('clicked()'), self, TQ_SLOT('newGameClicked()'))
|
|
@quit = TQt::PushButton.new('Quit', self)
|
|
connect(@quit, TQ_SIGNAL('clicked()'), $qApp, TQ_SLOT('quit()'))
|
|
b = TQt::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
|