smagacor 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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