smagacor 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Binary file
Binary file
@@ -2,6 +2,6 @@
2
2
  name: TicTacToe
3
3
  icon: tictactoe.png
4
4
  file: tictactoe.rb
5
- classname: Smagacor::TicTacToe::TicTacToeUI
5
+ classname: Smagacor::TicTacToe::TicTacToeGame
6
6
  description: This is the well known game TicTacToe
7
7
  category: Board Games
Binary file
@@ -1,7 +1,7 @@
1
1
  #
2
2
  #--
3
3
  #
4
- # $Id: tictactoe.rb 190 2005-02-02 19:26:25Z thomas $
4
+ # $Id: tictactoe.rb 362 2005-11-26 17:56:46Z thomas $
5
5
  #
6
6
  # smagacor - a collection of small games in ruby
7
7
  # Copyright (C) 2004 Thomas Leitner
@@ -19,280 +19,369 @@
19
19
  #
20
20
  #++
21
21
  #
22
- require 'observer'
22
+ require 'smagacor/listener'
23
23
 
24
- module Smagacor
24
+ # TicTacToe for Smagacor
25
+ module Smagacor::TicTacToe
25
26
 
26
- # TicTacToe for Smagacor
27
- module TicTacToe
27
+ # Base player class
28
+ class Player
28
29
 
29
- # Base player class
30
- class Player
30
+ # Return a new player with the +sign+.
31
+ def initialize
32
+ @field = -1
33
+ end
31
34
 
32
- # Defines whether the player can currently make a move. Default is +false+.
33
- attr_accessor :can_move
35
+ # Defines whether the player has selected a field. Default is +false+.
36
+ def field_selected?
37
+ @field != -1
38
+ end
34
39
 
35
- # Return a new player.
36
- def initialize
37
- @can_move = false
38
- end
40
+ # Invoked by the game engine to get the field. The field is reset to the standard value so that
41
+ # #field_selected? returns false.
42
+ def selected_field
43
+ field, @field = @field, -1
44
+ field
45
+ end
46
+
47
+ end
39
48
 
40
- # Invoked by the game engine to get the move.
41
- def move() end
42
49
 
50
+ # Human player. Provides the interface for a human to play TicTacToe.
51
+ class HumanPlayer < Player
52
+
53
+ # Set the +field+ which is returned by the next #selcted_field.
54
+ def set_field( field )
55
+ @field = field
43
56
  end
44
57
 
58
+ end
45
59
 
46
- # Human player. Provides the interface for a human to play TicTacToe.
47
- class HumanPlayer < Player
48
60
 
49
- def initialize
50
- super
51
- @field = -1
52
- end
61
+ # AI computer player. Not yet implemented!
62
+ class ComputerPlayer < Player
53
63
 
54
- # See Player#move
55
- def move
56
- self.can_move = false
57
- @field
58
- end
64
+ def selected_field
65
+ end
59
66
 
60
- # Set the +field+ which is returned by the next #move. <tt>can_move</tt> is set to +true+ so
61
- # that the game engine knows the this player has chosen a field.
62
- def set_field( field )
63
- @field = field
64
- self.can_move = true
65
- end
67
+ end
68
+
69
+
70
+ # The TicTacToe board.
71
+ class Board < String
72
+
73
+ # Defines the empty field.
74
+ FIELD_EMPTY = ?-
75
+
76
+ def initialize( size )
77
+ super( FIELD_EMPTY.chr * size )
78
+ end
66
79
 
80
+ # Redefined each so that the methods from Enumerable work correctly.
81
+ def each( &block )
82
+ each_byte( &block )
67
83
  end
68
84
 
85
+ end
86
+
87
+
88
+ # Game engine for TicTacToe. Knows how to correctly play TicTacToe.
89
+ class TicTacToe
90
+
91
+ PLAYER1 = ?1
92
+ PLAYER2 = ?2
93
+
94
+ include Listener
69
95
 
70
- # AI computer player. Not yet implemented!
71
- class ComputerPlayer < Player
96
+ # The board on which is played.
97
+ attr_accessor :board
72
98
 
73
- def move
99
+ # The current player.
100
+ attr_reader :cur_player
101
+
102
+ # Create a new TicTacToe game with the given +players+.
103
+ def initialize( player1, player2 )
104
+ @player1 = player1
105
+ @player2 = player2
106
+ @board = Board.new( 9 )
107
+ add_msg_name( :move )
108
+ add_msg_name( :finished )
109
+ end
110
+
111
+ # Initialize the game state.
112
+ def init
113
+ @board = Board.new( 9 )
114
+ @cur_player = @player1
115
+ end
116
+
117
+ # Play one (or more) round(s) of a TicTacToe game. When the current player has made his move
118
+ # (Player#field_selected?), his move is verified and the board changed. Then it is the turn of the
119
+ # other player. As long as the current player can make a move (and the game is not finished),
120
+ # the method runs. When the current player cannot make a move, the method exists and it has to
121
+ # be called again, when the current player can make his move.
122
+ def play_round
123
+ while @cur_player.field_selected? && !game_finished?
124
+ logger.info { "Current board: #{@board}" }
125
+ logger.info { "Current player: #{sign(@cur_player).chr}" }
126
+ field = @cur_player.selected_field
127
+ logger.info { "Field selected: #{field}" }
128
+ if @board[field] == Board::FIELD_EMPTY
129
+ @board[field] = sign( @cur_player )
130
+ switch_players
131
+ dispatch_msg( :move, field, @board[field] )
132
+ end
74
133
  end
134
+ if game_finished?
135
+ logger.info { "Game finished" }
136
+ dispatch_msg( :finished, player_won )
137
+ end
138
+ end
139
+
140
+ #######
141
+ private
142
+ #######
75
143
 
144
+ def sign( player )
145
+ ( player == @player1 ? PLAYER1 : PLAYER2 )
76
146
  end
77
147
 
148
+ def switch_players
149
+ @cur_player = ( @cur_player == @player1 ? @player2 : @player1 )
150
+ end
78
151
 
79
- # The TicTacToe board.
80
- class Board < String
152
+ # True if the board is full.
153
+ def board_full?
154
+ @board.all? {|i| i != Board::FIELD_EMPTY}
155
+ end
81
156
 
82
- # Defines the empty field.
83
- FIELD_EMPTY = ?-
157
+ # True if the game is over
158
+ def game_finished?
159
+ board_full? || player_won?( PLAYER1 ) || player_won?( PLAYER2 )
160
+ end
84
161
 
85
- def initialize( size )
86
- super( FIELD_EMPTY.chr * size )
87
- end
162
+ # Return the number of the player who has won.
163
+ #
164
+ # Returns
165
+ # 0:: player 1
166
+ # 1:: player 2
167
+ # 2:: draw
168
+ # -1:: game not finished yet
169
+ def player_won
170
+ ( player_won?( PLAYER1 ) ? 0 : ( player_won?( PLAYER2 ) ? 1 : ( board_full? ? 2 : -1 ) ) )
171
+ end
88
172
 
89
- # Redefined each so that the methods from Enumerable work correctly.
90
- def each( &block )
91
- each_byte( &block )
92
- end
173
+ # Win situations.
174
+ WIN_SITUATIONS = [[0,1,2], [3,4,5], [6,7,8], [0,3,6],
175
+ [1,4,7], [2,5,8], [0,4,8], [2,4,6]]
93
176
 
177
+ # Check if +player+ has won the game.
178
+ def player_won?( player )
179
+ WIN_SITUATIONS.each {|a,b,c| return true if @board[a] == player && @board[b] == player && @board[c] == player }
180
+ return false
94
181
  end
95
182
 
183
+ end
96
184
 
97
- # Game engine for TicTacToe. Knows how to correctly play TicTacToe.
98
- class TicTacToe
99
185
 
100
- include Observable
186
+ class TicTacToeCanvas < Qt::Widget
101
187
 
102
- # The board on which is played.
103
- attr_accessor :board
188
+ signals 'clicked(int)'
104
189
 
105
- # Create a new TicTacToe game with the given +players+.
106
- def initialize( players )
107
- @players = players
108
- @board = Board.new( 9 )
109
- end
190
+ def initialize( *args )
191
+ super( *args )
192
+ setBackgroundMode( Qt::NoBackground )
193
+ @x = @sx = Qt::Image.new( File.join( File.dirname( __FILE__ ), 'x.png' ) )
194
+ @o = @so = Qt::Image.new( File.join( File.dirname( __FILE__ ), 'o.png' ) )
195
+ end
110
196
 
111
- # Return the current player if the game has been initialized.
112
- def cur_player
113
- @players[@cur_player] if @cur_player < 2
114
- end
197
+ def game=( game )
198
+ @game = game
199
+ @game.add_msg_listener( :move, method(:onMove) )
200
+ end
115
201
 
116
- # Initialize the game state.
117
- def init
118
- @board = Board.new( 9 )
119
- @cur_player = 0
202
+ def onMove( field, player )
203
+ painter = Qt::Painter.new( self )
204
+ case player
205
+ when TicTacToe::PLAYER1 then paintX( painter, field )
206
+ when TicTacToe::PLAYER2 then paintO( painter, field )
120
207
  end
208
+ end
209
+
210
+ def paintPlayArea( painter )
211
+ painter.setPen( Qt::Pen.new( Qt::black, @b_padding/10, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin ) )
212
+ painter.drawLine( @b_padding + @b_width/3, @b_padding, @b_padding + @b_width/3, @b_padding + @b_width )
213
+ painter.drawLine( @b_padding + @b_width*2/3, @b_padding, @b_padding + @b_width*2/3, @b_padding + @b_width )
214
+ painter.drawLine( @b_padding, @b_padding + @b_width/3, @b_padding + @b_width, @b_padding + @b_width/3 )
215
+ painter.drawLine( @b_padding, @b_padding + @b_width*2/3, @b_padding + @b_width, @b_padding + @b_width*2/3 )
216
+ end
121
217
 
122
- # Play one (or more) round(s) of a TicTacToe game. When the current player can make a move
123
- # (Player#can_move), his move is verified and the board changed. Then it is the turn of the
124
- # other player. As long as the current player can make a move (and the game is not finished),
125
- # the method runs. When the current player cannot make a move, the method exists and it has to
126
- # be called again, when the current player can make his move.
127
- def play_round
128
- while @players[@cur_player].can_move && !game_finished?
129
- logger.info { "Current board: #{@board}" }
130
- logger.info { "Current player: #{cur_player}" }
131
- field = @players[@cur_player].move
132
- logger.info { "Field selected: #{field}" }
133
- if @board[field] == Board::FIELD_EMPTY
134
- @board[field] = @cur_player.to_s
135
- @cur_player = 1 - @cur_player
136
- changed
137
- notify_observers( :move )
218
+ def paintCurGame( painter )
219
+ unless @game.nil?
220
+ @game.board.each_with_index do |item, index|
221
+ case item
222
+ when TicTacToe::PLAYER1 then paintX( painter, index )
223
+ when TicTacToe::PLAYER2 then paintO( painter, index )
138
224
  end
139
225
  end
140
- if game_finished?
141
- logger.info { " Game finished" }
142
- changed
143
- notify_observers( :finished, player_won )
144
- end
145
226
  end
227
+ end
146
228
 
147
- private
229
+ def paintX( painter, field )
230
+ x, y, w = field_info( field )
231
+ @sx = @x.smoothScale( w, w ) if @sx.width != w
232
+ painter.drawImage( x, y, @sx )
233
+ end
148
234
 
149
- # True if the board is full.
150
- def board_full?
151
- @board.all? {|i| i != Board::FIELD_EMPTY}
152
- end
235
+ def paintO( painter, field )
236
+ x, y, w = field_info( field )
237
+ @so = @o.smoothScale( w, w ) if @so.width != w
238
+ painter.drawImage( x, y, @so )
239
+ end
153
240
 
154
- # Win situations.
155
- WIN_SITUATIONS = [[0,1,2], [3,4,5], [6,7,8], [0,3,6],
156
- [1,4,7], [2,5,8], [0,4,8], [2,4,6]]
241
+ def paintEvent( event )
242
+ pix = Qt::Pixmap.new( width, height )
243
+ painter = Qt::Painter.new
244
+ painter.begin( pix )
245
+ painter.setBrush( Qt::white )
246
+ painter.drawRect( 0, 0, self.width, self.height )
247
+ paintPlayArea( painter )
248
+ paintCurGame( painter )
249
+ painter.end
250
+
251
+ p = Qt::Painter.new( self )
252
+ p.drawPixmap( 0, 0, pix )
253
+ end
157
254
 
158
- # True if the game is over
159
- def game_finished?
160
- board_full? || player_won?( ?0 ) || player_won?( ?1 )
161
- end
255
+ def resizeEvent( event )
256
+ width = (self.width > self.height ? self.height : self.width )
257
+ @b_width = (width * 0.8).to_i
258
+ @b_padding = (width * 0.1).to_i
259
+ @f_width = @b_width/5
260
+ @f_padding = @b_padding + (@b_width/3 - @f_width)/2
261
+ end
162
262
 
163
- # Return the number of the player who has won.
164
- #
165
- # Returns
166
- # 0:: player 1
167
- # 1:: player 2
168
- # 2:: draw
169
- # -1:: game not finished yet
170
- def player_won
171
- ( player_won?( ?0 ) ? 0 : ( player_won?( ?1 ) ? 1 : ( board_full? ? 2 : -1 ) ) )
172
- end
263
+ def field_info( field )
264
+ [@f_padding + @b_width/3 * (field % 3), @f_padding + @b_width/3 * (field / 3), @f_width]
265
+ end
173
266
 
174
- # Check if +player+ has won the game.
175
- def player_won?( player )
176
- WIN_SITUATIONS.each {|a,b,c| return true if @board[a] == player && @board[b] == player && @board[c] == player }
177
- return false
267
+ def mouseReleaseEvent( event )
268
+ pos = Proc.new do |point|
269
+ case point
270
+ when 0..(@b_padding + @b_width/3): 0
271
+ when (@b_padding + @b_width/3)..(@b_padding + @b_width*2/3): 1
272
+ else 2
273
+ end
178
274
  end
179
-
275
+ emit clicked( pos[event.x] + pos[event.y]*3 )
180
276
  end
181
277
 
278
+ end
182
279
 
183
- # Widget for TicTacToe.
184
- class TicTacToeUI < FXHorizontalFrame
185
280
 
186
- attr_reader :gameinfo
281
+ # Widget for TicTacToe.
282
+ class TicTacToeUI < Qt::Widget
187
283
 
188
- def initialize( p, gameinfo )
189
- super( p )
190
- @gameinfo = gameinfo
284
+ slots 'start_game()', 'clicked(int)'
191
285
 
192
- @canvas = FXCanvas.new( self, nil, 0, LAYOUT_FILL_X|LAYOUT_FILL_Y )
193
- @canvas.connect( SEL_PAINT, method( :onPaint ) )
194
- @canvas.connect( SEL_LEFTBUTTONRELEASE, method( :onCanvasClick ) )
195
- FXVerticalSeparator.new( self )
196
- options = FXVerticalFrame.new( self, LAYOUT_FILL_Y )
286
+ attr_reader :gameinfo
197
287
 
198
- p = Proc.new do |name|
199
- FXLabel.new( options, name )
200
- box = FXComboBox.new( options, 20, 2, nil, 0, COMBOBOX_NORMAL|COMBOBOX_STATIC|FRAME_SUNKEN )
201
- box.appendItem( 'Human', HumanPlayer )
202
- box.appendItem( 'Computer', ComputerPlayer )
203
- box
204
- end
205
- @player1 = p.call( 'Player 1' )
206
- @player2 = p.call( 'Player 2' )
288
+ PLAYERS = {'Human' => HumanPlayer, 'Computer' => ComputerPlayer}
207
289
 
208
- FXButton.new( options, "Start Game", nil, nil, 0, LAYOUT_FILL_X|BUTTON_NORMAL ).connect( SEL_COMMAND, method( :onStartGame ) )
290
+ def initialize( p, gameinfo )
291
+ super( p )
292
+ @gameinfo = gameinfo
209
293
 
210
- @xicon = File.join( gameinfo.directory, 'x.png' )
211
- @oicon = File.join( gameinfo.directory, 'o.png' )
212
- end
294
+ @canvas = TicTacToeCanvas.new( self )
295
+ connect( @canvas, SIGNAL('clicked(int)'), self, SLOT('clicked(int)') )
213
296
 
214
- def create
215
- super
297
+ optionsLayout = Qt::VBoxLayout.new
298
+ optionsLayout.setSpacing( 3 )
299
+ optionsLayout.setMargin( 6 )
300
+ p = Proc.new do |name|
301
+ optionsLayout.addWidget( Qt::Label.new( name, self ) )
302
+ box = Qt::ComboBox.new( false, self )
303
+ optionsLayout.addWidget( box )
304
+ box.insertItem( 'Human' )
305
+ box.insertItem( 'Computer' )
306
+ box
216
307
  end
308
+ @player1 = p.call( 'Player 1' )
309
+ @player2 = p.call( 'Player 2' )
217
310
 
218
- def onCanvasClick( sender, sel, event )
219
- if !@game.nil? && @game.cur_player.respond_to?( :set_field )
220
- width, padding = get_lengths
221
- pos = Proc.new do |point|
222
- case point
223
- when 0..(padding + width/3): 0
224
- when (padding + width/3)..(padding + width*2/3): 1
225
- else 2
226
- end
227
- end
228
- @game.cur_player.set_field( pos[event.click_x] + pos[event.click_y]*3 )
229
- @game.play_round
230
- end
231
- end
311
+ button = Qt::PushButton.new( "Start Game", self )
312
+ connect( button, SIGNAL('clicked()'), self, SLOT('start_game()') )
232
313
 
233
- def onStartGame( sender, sel, event )
234
- p = [@player1.getItemData( @player1.currentItem ).new, @player2.getItemData( @player2.currentItem ).new ]
235
- @canvas.update
236
- @game = TicTacToe.new( p)
237
- @game.add_observer( self )
238
- @game.init
314
+ optionsLayout.addSpacing( 10 )
315
+ optionsLayout.addWidget( button )
316
+ optionsLayout.addStretch
317
+
318
+ @xicon = File.join( gameinfo.directory, 'x.png' )
319
+ @oicon = File.join( gameinfo.directory, 'o.png' )
320
+
321
+ layout = Qt::HBoxLayout.new( self )
322
+ layout.addWidget( @canvas, 1 )
323
+ layout.addLayout( optionsLayout )
324
+ end
325
+
326
+ def clicked( field )
327
+ if !@game.nil? && @game.cur_player.respond_to?( :set_field )
328
+ @game.cur_player.set_field( field )
239
329
  @game.play_round
240
330
  end
331
+ end
241
332
 
242
- def update( reason, winner=nil )
243
- case reason
244
- when :move: @canvas.update
245
- when :finished
246
- msg = (winner < 2 ? "Player #{winner+1}" : "Nobody" ) + " has won!"
247
- FXMessageBox.information( self, MBOX_OK, "Result", msg )
248
- end
249
- end
333
+ def start_game
334
+ @game = TicTacToe.new( PLAYERS[@player1.currentText].new, PLAYERS[@player2.currentText].new )
335
+ @canvas.game = @game
336
+ @game.add_msg_listener( :move, method(:onMove) )
337
+ @game.add_msg_listener( :finished, method(:finished) )
338
+ @game.init
339
+ @game.play_round
340
+ @canvas.update
341
+ end
250
342
 
251
- def onPaint( sender, sel, event )
252
- FXDCWindow.new( sender, event ) do |dc|
253
- dc.foreground = FXColor::White
254
- dc.fillRectangle( 0, 0, sender.width, sender.height )
255
- paintPlayArea( dc )
256
- paintCurGame( dc )
257
- end
258
- end
343
+ def onMove( field, player )
344
+ @manager.add_undo_info( [field, player] ) # TODO think about AI moves, player changes etc
345
+ end
259
346
 
260
- private
347
+ def finished( winner )
348
+ msg = (winner < 2 ? "Player #{winner+1}" : "Nobody" ) + " has won!"
349
+ Qt::MessageBox.information( self, "Result", msg, Qt::MessageBox::Ok )
350
+ end
261
351
 
262
- def get_lengths
263
- width = (@canvas.width > @canvas.height ? @canvas.height : @canvas.width )
264
- [(width * 0.8).to_i, (width * 0.1).to_i]
265
- end
352
+ def set_undo_manager( manager )
353
+ @manager = manager
354
+ manager.add_msg_listener( :undo, method(:undo) )
355
+ manager.add_msg_listener( :redo, method(:redo) )
356
+ end
266
357
 
267
- def paintPlayArea( dc )
268
- width, padding = get_lengths
269
- dc.foreground = FXColor::Black
270
- dc.lineWidth = 10
271
- dc.lineCap = CAP_ROUND
272
- dc.drawLine( padding + width/3, padding, padding + width/3, padding + width )
273
- dc.drawLine( padding + width*2/3, padding, padding + width*2/3, padding + width )
274
- dc.drawLine( padding, padding + width/3, padding + width, padding + width/3 )
275
- dc.drawLine( padding, padding + width*2/3, padding + width, padding + width*2/3 )
276
- end
358
+ def undo( info )
359
+ #TODO also undo cur player
360
+ @game.board[info[0]] = Board::FIELD_EMPTY
361
+ @canvas.update
362
+ end
277
363
 
278
- def paintCurGame( dc )
279
- unless @game.nil?
280
- width, padding = get_lengths
281
- size = width/5
282
- padding = padding + (width/3 - size)/2
283
- @game.board.each_with_index do |item, index|
284
- case item
285
- when ?0
286
- dc.foreground = FXColor::Red
287
- dc.drawRectangle( padding + width/3 * (index % 3), padding + width/3 * (index / 3), size, size )
288
- when ?1
289
- dc.foreground = FXColor::Green
290
- dc.drawCircle( padding + size/2 + width/3 * (index % 3), padding + size/2 + width/3 * (index / 3), size/2 )
291
- end
292
- end
293
- end
294
- end
364
+ def redo( info )
365
+ @game.board[info[0]] = info[1]
366
+ @canvas.update
367
+ end
368
+
369
+ end
370
+
371
+
372
+ class TicTacToeGame < Smagacor::GamePlugin
373
+
374
+ def initialize( p, gameinfo )
375
+ @parent = p
376
+ @widget = TicTacToeUI.new( p, gameinfo )
377
+ end
378
+
379
+ def game_widget
380
+ @widget
381
+ end
295
382
 
383
+ def set_undo_manager( manager )
384
+ @widget.set_undo_manager( manager )
296
385
  end
297
386
 
298
387
  end