Class: Board1010

Inherits:
Object
  • Object
show all
Defined in:
lib/games_paradise/gui/gosu/1010/board.rb

Overview

#

require ‘games_paradise/gui/gosu/1010/board.rb’

#

Constant Summary collapse

MAX_ROWS =

MAX_ROWS

10
MAX_COLS =

MAX_COLS

10
MAX_OPTIONS =

MAX_OPTIONS

3
GAME_FILE_NAME =

GAME_FILE_NAME

RUBY_ENGINE == "mruby" ? "./1010game.str" : "./1010game.dat"
DOT =
[[1]].freeze
HL =

Horizontal Lines

[[], [[1], [0]], [[2, 2], [0, 0]], [[3, 3, 3], [0, 0, 0]], [[4, 4, 4, 4], [0, 0, 0, 0]], [[5, 5, 5, 5, 5], [0, 0, 0, 0, 0]]].freeze
VL =

Vertical Lines

[[], [[1, 0]], [[1, 0], [1, 0]], [[1, 0], [1, 0], [1, 0]], [[1, 0], [1, 0], [1, 0], [1, 0]], [[1, 0], [1, 0], [1, 0], [1, 0], [1, 0]]].freeze
SQ =

Squares

[
  [], [[1]],
  [[2, 2],
   [2, 2]],
  [[3, 3, 3],
   [3, 3, 3],
   [3, 3, 3]]
].freeze
BL =

Bottom Left Corners

[
  [], [[1]],
  [[2, 0],
   [2, 2]],
  [[3, 0, 0],
   [3, 0, 0],
   [3, 3, 3]]
].freeze
TL =

Top Left Corners

[
  [], [[1]],
  [[2, 2],
   [0, 2]],
  [[3, 3, 3],
   [0, 0, 3],
   [0, 0, 3]]
].freeze
BR =

Bottom Right Corners

[
  [], [[1]],
  [[0, 2],
   [2, 2]],
  [[0, 0, 3],
   [0, 0, 3],
   [3, 3, 3]]
].freeze
TR =

Top Right Corners

[
  [], [[1]],
  [[2, 2],
   [2, 0]],
  [[3, 3, 3],
   [3, 0, 0],
   [3, 0, 0]]
].freeze
ALL_TILES =
[
  DOT,

  HL[2],
  HL[3],
  HL[4],
  HL[5],

  VL[2],
  VL[3],
  VL[4],
  VL[5],

  SQ[2],
  SQ[3],

  BL[2],
  BL[3],

  TL[2],
  TL[3],

  BR[2],
  BR[3],

  TR[2],
  TR[3]
].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(rows = MAX_ROWS, columns = MAX_COLS) ⇒ Board1010

#

initialize

#


120
121
122
123
124
125
126
127
# File 'lib/games_paradise/gui/gosu/1010/board.rb', line 120

def initialize(
    rows=MAX_ROWS, columns=MAX_COLS
  )
  @rows    = rows
  @cols    = columns
  @prev_options = @prev_score = @prev_arr = nil
  reset
end

Instance Attribute Details

#arrObject

Returns the value of attribute arr.



13
14
15
# File 'lib/games_paradise/gui/gosu/1010/board.rb', line 13

def arr
  @arr
end

#colsObject

Returns the value of attribute cols.



12
13
14
# File 'lib/games_paradise/gui/gosu/1010/board.rb', line 12

def cols
  @cols
end

#optionsObject

Returns the value of attribute options.



15
16
17
# File 'lib/games_paradise/gui/gosu/1010/board.rb', line 15

def options
  @options
end

#placed_posObject

Returns the value of attribute placed_pos.



17
18
19
# File 'lib/games_paradise/gui/gosu/1010/board.rb', line 17

def placed_pos
  @placed_pos
end

#prev_arrObject (readonly)

Returns the value of attribute prev_arr.



19
20
21
# File 'lib/games_paradise/gui/gosu/1010/board.rb', line 19

def prev_arr
  @prev_arr
end

#rowsObject

Returns the value of attribute rows.



11
12
13
# File 'lib/games_paradise/gui/gosu/1010/board.rb', line 11

def rows
  @rows
end

#scoreObject

Returns the value of attribute score.



14
15
16
# File 'lib/games_paradise/gui/gosu/1010/board.rb', line 14

def score
  @score
end

#valObject

Returns the value of attribute val.



16
17
18
# File 'lib/games_paradise/gui/gosu/1010/board.rb', line 16

def val
  @val
end

Instance Method Details

#andy_algo(_positions) ⇒ Object

andy_algo



539
540
541
542
543
544
545
# File 'lib/games_paradise/gui/gosu/1010/board.rb', line 539

def andy_algo(_positions)
  ## Suggested by Andy:
  #
  ## Find the position which results into minimum empty cells after placing the tile but before
  ## clearing the filled rows and columns
  -1
end

#backtrackObject

backtrack



353
354
355
356
357
358
# File 'lib/games_paradise/gui/gosu/1010/board.rb', line 353

def backtrack
  @placed_pos.each { |i, j, _k|
    _set_cell(i, j, 0)
  }
  @placed_pos = []
end

#best_starting_position(tile) ⇒ Object

best_starting_position



585
586
587
588
589
590
591
592
593
594
595
# File 'lib/games_paradise/gui/gosu/1010/board.rb', line 585

def best_starting_position(tile)
  positions = find_starting_pos(tile)
  # n = rand_fit(positions)
  # n = first_fit(positions)
  # n = last_fit(positions)
  # n = shanko_algo(positions)
  n = jc_algo(positions)
  # n = sam_algo(positions)
  # n = andy_algo(positions)
  positions[n || 0]
end

#cell=(triplet) ⇒ Object



278
279
280
281
# File 'lib/games_paradise/gui/gosu/1010/board.rb', line 278

def cell=(triplet)
  i, j, val = triplet
  _set_cell(i, j, val)
end

#cleanup(dry_run = false) ⇒ Object



319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
# File 'lib/games_paradise/gui/gosu/1010/board.rb', line 319

def cleanup(dry_run=false)
  rows_to_clean = []
  @rows.times do |i|
    row_full = true
    @cols.times do |j|
      if _cell_empty?(i, j)
        row_full = false
        break
      end
    end
    rows_to_clean << i if row_full
  end

  cols_to_clean = []
  @cols.times do |j|
    col_full = true
    @rows.times do |i|
      if _cell_empty?(i, j)
        col_full = false
        break
      end
    end
    cols_to_clean << j if col_full
  end

  unless dry_run
    rows_to_clean.each {|i| row_cleanup(i) }
    cols_to_clean.each {|j| col_cleanup(j) }
  end

  [rows_to_clean, cols_to_clean]
end

#col_cleanup(j) ⇒ Object



313
314
315
316
317
# File 'lib/games_paradise/gui/gosu/1010/board.rb', line 313

def col_cleanup(j)
  @rows.times do |i|
    _set_cell(i, j, @val)
  end
end

#current_score?Boolean Also known as: current_score

#

current_score?

Feedback the current score.

#

Returns:

  • (Boolean)


263
264
265
# File 'lib/games_paradise/gui/gosu/1010/board.rb', line 263

def current_score?
  @score
end

#endedObject

ended



452
453
454
455
# File 'lib/games_paradise/gui/gosu/1010/board.rb', line 452

def ended
  e "\n--- Game ended: no place for remaining tiles"
  _over
end

#find_fitting_tileObject

find_fitting_tile

Improve this algorithm to find a better fit



368
369
370
371
372
373
# File 'lib/games_paradise/gui/gosu/1010/board.rb', line 368

def find_fitting_tile
  # e "*** Finding a fitting tile ***"
  tiny_tiles = [HL[2], VL[2], SQ[2], BL[2], TL[2], BR[2], TR[2], DOT]
  _i, _j, tile = pos_exists?(tiny_tiles)
  tile
end

#find_starting_pos(tile) ⇒ Object

find_starting_pos



483
484
485
486
487
488
489
490
491
492
# File 'lib/games_paradise/gui/gosu/1010/board.rb', line 483

def find_starting_pos(tile)
  positions = []
  @arr.each_with_index do |row, i|
    row.each_with_index do |_col, j|
      score = place(tile, i, j, true)
      positions << [i, j] if score > 0
    end
  end
  positions
end

#first_fit(_positions) ⇒ Object



569
570
571
572
# File 'lib/games_paradise/gui/gosu/1010/board.rb', line 569

def first_fit(_positions)
  # The best position is always the first position found
  0
end

#generate_tiles(n = Board1010::MAX_OPTIONS) ⇒ Object



375
376
377
378
379
380
381
382
383
384
385
386
387
# File 'lib/games_paradise/gui/gosu/1010/board.rb', line 375

def generate_tiles(n=Board1010::MAX_OPTIONS)
  opt_tiles = []

  n.times do |_i|
    r = rand(ALL_TILES.size)
    tile = ALL_TILES[r]
    opt_tiles << tile
  end

  opt_tiles[0] = find_fitting_tile unless pos_exists?(opt_tiles)

  opt_tiles
end

#get_positionObject

#

get_position

#


297
298
299
300
301
302
303
304
305
# File 'lib/games_paradise/gui/gosu/1010/board.rb', line 297

def get_position
  print "Select Row, Col: "
  line = gets.chomp
  x, y = line.split(",")
  stop if x.to_i < 0 || y.to_i < 0
  i = x.to_i % Board1010::MAX_ROWS
  j = y.to_i % Board1010::MAX_COLS
  [i, j]
end

#get_selected_option(i, j) ⇒ Object



283
284
285
286
287
288
289
290
291
292
# File 'lib/games_paradise/gui/gosu/1010/board.rb', line 283

def get_selected_option(i, j)
  e "Select one option for position (#{i}, #{j}):"
  loop do
    print "option> "
    option = gets.chomp
    stop if option.to_s.casecmp("q").zero?
    return option.to_i if [1, 2, 3].include?(option.to_i)
    e "Invalid option #{option}. Should be one of [1,2,3]"
  end
end

#init(val = nil) ⇒ Object

init



233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/games_paradise/gui/gosu/1010/board.rb', line 233

def init(val = nil)
  case val.class
  when Array
    @arr   = val
    @score = _filled_cells
  when Numeric
    @val = val
    @arr = Array.new(@rows) { Array.new(@cols) { val } }
    @score = 0
    @options = []
  else
    if game = load_game
      @rows    = Board1010::MAX_ROWS
      @cols    = Board1010::MAX_COLS
      @val     = 0
      @arr     = game.arr
      @score   = game.score
      @options = game.options
    else
      @arr = Array.new(@rows) { Array.new(@cols) { @val } }
    end
  end
  start
end

#jc_algo(positions) ⇒ Object



547
548
549
550
551
552
553
554
555
556
# File 'lib/games_paradise/gui/gosu/1010/board.rb', line 547

def jc_algo(positions)
  # Suggested by JC:
  # The best position is one which has most neighbors occupied.
  # But treat the edge cells as if their neighbors are always occupied.
  neighbour_count = []
  positions.each_with_index do |pos, k|
    neighbour_count[k] = jc_neighbours(*pos)
  end
  neighbour_count.index(neighbour_count.max)
end

#jc_neighbours(i, j) ⇒ Object

jc_neighbours



512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
# File 'lib/games_paradise/gui/gosu/1010/board.rb', line 512

def jc_neighbours(i, j)
  count = 0

  count += 1 if i > 0 && j > 0 && (@arr[i - 1][j - 1]).nonzero?
  count += 1 if i > 0 && (@arr[i - 1][j]).nonzero?
  count += 1 if i > 0 && j < 9 && (@arr[i - 1][j + 1]).nonzero?

  count += 1 if i < 9 && j > 0 && (@arr[i + 1][j - 1]).nonzero?
  count += 1 if i < 9 && (@arr[i + 1][j]).nonzero?
  count += 1 if i < 9 && j < 9 && (@arr[i + 1][j + 1]).nonzero?

  count += 1 if j > 0 && (@arr[i][j - 1]).nonzero?
  count += 1 if j < 9 && (@arr[i][j + 1]).nonzero?

  count += 1 if i.zero? || (i == 9)
  count += 1 if j.zero? || (j == 9)

  count
end

#last_fit(_positions) ⇒ Object



574
575
576
577
# File 'lib/games_paradise/gui/gosu/1010/board.rb', line 574

def last_fit(_positions)
  # The best position is always the last position found
  -1
end

#load_gameObject



202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/games_paradise/gui/gosu/1010/board.rb', line 202

def load_game
  game = nil
  if File.exist?(GAME_FILE_NAME)
    File.open(GAME_FILE_NAME, "rb") do |f|
      data = f.read
      game = if RUBY_ENGINE == "mruby"
               parse_load(data)
             else
               Marshal.load(data)
             end
    end
  end
  game
end

#marshal_dumpObject

#

marshal_dump

Save important stuff, such as the user’s score.

#


151
152
153
# File 'lib/games_paradise/gui/gosu/1010/board.rb', line 151

def marshal_dump
  [@score, @arr, @options]
end

#marshal_load(array) ⇒ Object

#

marshal_load

#


158
159
160
# File 'lib/games_paradise/gui/gosu/1010/board.rb', line 158

def marshal_load(array)
  @score, @arr, @options = array
end

#neighbours(i, j) ⇒ Object



494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
# File 'lib/games_paradise/gui/gosu/1010/board.rb', line 494

def neighbours(i, j)
  count = 0

  count += 1 if i > 0 && j > 0 && (@arr[i - 1][j - 1]).nonzero?
  count += 1 if i > 0 && (@arr[i - 1][j]).nonzero?
  count += 1 if i > 0 && j < 9 && (@arr[i - 1][j + 1]).nonzero?

  count += 1 if i < 9 && j > 0 && (@arr[i + 1][j - 1]).nonzero?
  count += 1 if i < 9 && (@arr[i + 1][j]).nonzero?
  count += 1 if i < 9 && j < 9 && (@arr[i + 1][j + 1]).nonzero?

  count += 1 if j > 0 && (@arr[i][j - 1]).nonzero?
  count += 1 if j < 9 && (@arr[i][j + 1]).nonzero?

  count
end

#parse_load(data) ⇒ Object

#

parse_load

#


177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/games_paradise/gui/gosu/1010/board.rb', line 177

def parse_load(data)
  bGame = Struct.new(
    'Game', :rows, :cols, :score, :val, :options, :arr
  )
  game = bGame.new
  data.split(';').each do |element|
    key, val = element.split("=")
    case key
    when '@rows'
      game.rows = val.to_i
    when '@cols'
      game.cols = val.to_i
    when '@score'
      game.score = val.to_i
    when '@val'
      game.val = val.to_i
    when '@options'
      game.options = [[1], [1, 1, 1], [-1, -1, -1]]
    when '@arr'
      game.arr = Array.new(game.rows) { Array.new(game.cols) { @val } }
    end
  end
  game
end

#place(tile, i, j, dry_run = false) ⇒ Object

#

place

#


600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
# File 'lib/games_paradise/gui/gosu/1010/board.rb', line 600

def place(tile, i, j, dry_run=false)
  score = 0 # Calculate the score.
  @placed_pos = []
  tile.each_with_index do |cell, r|
    if r.zero?
      cell.each { |entry|
        break if entry > 0
        j -= 1
      }
      return 0 if j < 0
    end
    cell.each_with_index do |ele, c|
      if ele > 0 && _cell_occupied?(i + r, j + c)
        backtrack unless dry_run
        return 0
      end
      next unless ele > 0
      _set_cell(i + r, j + c, ele) unless dry_run
      @placed_pos << [i + r, j + c, ele]
      score += 1
    end
  end
  # Add it to the main scrore.
  @score += score unless dry_run
  score
end

#playObject

play



420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
# File 'lib/games_paradise/gui/gosu/1010/board.rb', line 420

def play
  loop do
    all = (!@options.empty? ? @options : generate_tiles)
    @options = all
    until all.empty?
      show
      e "\n#{'=' * 20}\nCurrent score = #{current_score}\n#{'=' * 20}\n"
      show_tiles(all)
      ended unless pos_exists?(all)
      i, j = get_position
      opt = if all.size > 1
              get_selected_option(i, j)
            else
              1
            end
      tile = all[opt - 1]
      new_score = place(tile, i, j)
      if new_score < 1
        e " Cannot place the tile #{tile} at (#{i}, #{j})"
      else
        all.delete_at(opt - 1)
        cleanup
      end
    end
  end
end

#pos_exists?(tiles, debug = false) ⇒ Boolean

Returns:

  • (Boolean)


401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
# File 'lib/games_paradise/gui/gosu/1010/board.rb', line 401

def pos_exists?(tiles, debug=false)
  tiles.each do |tile|
    if debug
      e "Checking if tile #{tile} can be placed:"
      show
    end
    @arr.each_with_index do |row, i|
      row.each_with_index do |_col, j|
        score = (_cell_occupied?(i, j) ? 0 : place(tile, i, j, true))
        e "score = #{score} at (#{i},#{j})" if debug
        return [i, j, tile] if score > 0
      end
    end
  end
  e "...no can place" if debug
  false
end

#rand_fit(positions) ⇒ Object



579
580
581
582
# File 'lib/games_paradise/gui/gosu/1010/board.rb', line 579

def rand_fit(positions)
  # The best position is always a random position found
  rand(positions.size)
end

#resetObject

#

reset

#


132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/games_paradise/gui/gosu/1010/board.rb', line 132

def reset
  STDOUT.sync = true
  @arr     = nil
  # ======================================================================= #
  # === @score
  #
  # This variable keeps track of the user's current score. It will
  # start at score 0.
  # ======================================================================= #
  @score   = 0
  @val     = 0
  @options = []
end

#restore_arr(prev_arr) ⇒ Object

#

restore_arr

#


270
271
272
# File 'lib/games_paradise/gui/gosu/1010/board.rb', line 270

def restore_arr(prev_arr)
  @arr = Marshal.load(Marshal.dump(prev_arr))
end

#restore_options(prev_options) ⇒ Object



274
275
276
# File 'lib/games_paradise/gui/gosu/1010/board.rb', line 274

def restore_options(prev_options)
  @options = Marshal.load(Marshal.dump(prev_options))
end

#row_cleanup(i) ⇒ Object



307
308
309
310
311
# File 'lib/games_paradise/gui/gosu/1010/board.rb', line 307

def row_cleanup(i)
  @cols.times do |j|
    _set_cell(i, j, @val)
  end
end

#sam_algo(_positions) ⇒ Object



532
533
534
535
536
# File 'lib/games_paradise/gui/gosu/1010/board.rb', line 532

def sam_algo(_positions)
  ## Suggested by Sam:
  ## Find the position which completely fills more rows and columns when the tile is placed
  -1
end

#saveObject



217
218
219
220
221
222
223
224
225
226
227
228
229
230
# File 'lib/games_paradise/gui/gosu/1010/board.rb', line 217

def save
  print "Do you want to save the game to play it later? [y/n] "
  opt = gets.chomp.downcase
  if opt == "y"
    File.open(GAME_FILE_NAME, "wb") do |f|
      if RUBY_ENGINE == "mruby"
        f.write(to_s)
      else
        f.write(Marshal.dump(self))
      end
    end
  end
  (opt == "y")
end

#shanko_algo(positions) ⇒ Object

shanko_algo



559
560
561
562
563
564
565
566
567
# File 'lib/games_paradise/gui/gosu/1010/board.rb', line 559

def shanko_algo(positions)
  # The best position is one which has most neighbors occupied.
  # In other words: least neighboring cells empty
  neighbour_count = []
  positions.each_with_index { |pos, k|
    neighbour_count[k] = neighbours(*pos)
  }
  neighbour_count.index(neighbour_count.max)
end

#showObject

show



464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
# File 'lib/games_paradise/gui/gosu/1010/board.rb', line 464

def show
  e ''
  print "   "; @cols.times {|j| print " #{j}" }
  e
  @arr.each_with_index do |row, i|
    print " #{i}["
    row.each do |col|
      if col.zero?
        print " ."
      else
        print " X"
      end
    end
    e " ]"
  end
  e ''
end

#show_tile(tile) ⇒ Object

show_title



361
362
363
# File 'lib/games_paradise/gui/gosu/1010/board.rb', line 361

def show_tile(tile)
  p tile
end

#show_tiles(all) ⇒ Object

show_tiles



390
391
392
393
394
395
396
397
398
399
# File 'lib/games_paradise/gui/gosu/1010/board.rb', line 390

def show_tiles(all)
  n = 1
  all.each do |tile|
    next unless tile
    print " #{n}> "
    show_tile(tile)
    n += 1
  end
  n
end

#startObject



447
448
449
# File 'lib/games_paradise/gui/gosu/1010/board.rb', line 447

def start
  @start_time = Time.now
end

#stopObject



457
458
459
460
461
# File 'lib/games_paradise/gui/gosu/1010/board.rb', line 457

def stop
  e "\n--- Game stopped: to be played later"
  save
  _over
end

#to_sObject

#

to_s

#


165
166
167
168
169
170
171
172
# File 'lib/games_paradise/gui/gosu/1010/board.rb', line 165

def to_s
  opt_str = ''.dup
  @options.each { |opt|
    opt_str << opt.inject('') {|sum, c| sum + c.to_s }
    opt_str << ","
  }
  "@rows=#{@rows};@cols=#{@cols};@score=#{@score};@val=#{@val};@options=#{@options};@arr=#{@arr}"
end