summaryrefslogtreecommitdiff
path: root/bindings/ruby/quackle_runner.rb
blob: acf88cffba31530939fdd5d37aac62c73e8b362f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
require_relative 'quackle'

# Some higher-level functions over the quackle bindings.

module Quackle
  PLAYER_TYPES = {
    speedy: "Speedy Player",
    championship: "Championship Player",
    championship_20s: "Twenty Second Championship Player",
    championship_5m: "Five Minute Championship Player",
  }

  module GCGUtils
    def self.load_game(path)
      gamereader = GCGIO.new
      gamePath = Util.stdStringToQString(path)
      gamereader.read(gamePath, Logania::MaintainBoardPreparation)
    end
  end

  module BoardUtils
    SYMBOLS = {
      '~' => :w4, '=' => :w3, '-' => :w2,
      '^' => :l4, '"' => :l3, "'" => :l2
    }

    def self.make_board_params(ary)
      # Assume ary is a well-formed rectangular array
      h = ary.length
      w = ary[0].length
      b = BoardParameters.new()
      b.setHeight(h)
      b.setWidth(w)
      ary.each_with_index do |row, y|
        row.each_with_index do |cell, x|
          type, mul = cell.to_s.split(//)
          mul = mul.to_i
          if type == 'w'
            b.setWordMultiplier(y, x, mul)
          elsif type == 'l'
            b.setLetterMultiplier(y, x, mul)
          end
        end
      end
      return b
    end

    def self.read_board_params(str, piped: false, doublespaced: false)
      # Assume str is a board with one row per line.
      # Set doublespaced: true if cells are separated by
      # a space (as in quackle's board display, e.g.)
      # Set piped: true if rows are marked by a | on either side.
      rows = str.lines.map(&:chomp)
      if piped
        rows = rows.select {|r| r.include?('|')}
        rows = rows.map {|r| r.gsub(/^.*?[|](.*?)[|].*$/, '\1')}
      end
      cells = rows.map do |r|
        row = r.split(//)
        if doublespaced
          row = row.each_slice(2).map(&:first)
        end
        row.map {|c| SYMBOLS[c] || :l1}
      end
      make_board_params(cells)
    end
  end

  module RunnerUtils
    def initialize_dm(lexicon:, alphabet:, datadir:, board:nil)
      # Set up the data manager
      dm = DataManager.new
      dm.setComputerPlayers(ComputerPlayerCollection.fullCollection)
      dm.setBackupLexicon(lexicon)
      dm.setAppDataDirectory(datadir)

      # Set up the alphabet
      abc = AlphabetParameters.findAlphabetFile(alphabet)
      abc2 = Util.stdStringToQString(abc) #convert to qstring
      fa = FlexibleAlphabetParameters.new

      fail unless fa.load(abc2)
      dm.setAlphabetParameters(fa)

      # Set up the board
      board ||= BoardParameters.new
      dm.setBoardParameters(board)

      # Find the lexicon
      dawg = LexiconParameters.findDictionaryFile(lexicon + '.dawg')
      gaddag = LexiconParameters.findDictionaryFile(lexicon + '.gaddag')
      dm.lexiconParameters.loadDawg(dawg)
      dm.lexiconParameters.loadGaddag(gaddag)

      dm.strategyParameters.init(lexicon)
      return dm
    end

    def computer_player(type)
      name = PLAYER_TYPES[type]
      if name
        player, found = @dm.computerPlayers.playerForName(name)
        if not found
          raise NameError("Could not initialize computer player #{name}")
        end
        player.computerPlayer
      else
        raise ArgumentError("Unknown computer player: #{type}")
      end
    end
  end

  class AIRunner
    include RunnerUtils

    attr_accessor :dm, :game, :players

    def initialize(lexicon:, alphabet:, datadir:, board: nil, random_seed: nil)
      @dm = initialize_dm(
        lexicon: lexicon, alphabet: alphabet, datadir: datadir, board: board)
      @dm.seedRandomNumbers(random_seed) if random_seed
    end

    def init_game(players)
      @game = Game.new
      @players = PlayerList.new
      players.each_with_index do |(name, type), i|
        player = Player::new(name, Player::ComputerPlayerType, i)
        player.setComputerPlayer(computer_player(type))
        @players << player
      end

      game.setPlayers(@players)
      game.associateKnownComputerPlayers
      game.addPosition
    end

    def current_position
      game.currentPosition
    end

    def current_player
      current_position.currentPlayer
    end

    def board
      current_position.board
    end

    def make_move
      game.haveComputerPlay
    end

    def game_over?
      current_position.gameOver
    end
  end
end