linotype 0.0.1

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.
@@ -0,0 +1,107 @@
1
+ module Linotype
2
+ class Game
3
+
4
+ def initialize
5
+ @board = Board.new_random(self)
6
+ @players = [Player.new, Player.new]
7
+ @current_player = @players[0]
8
+ @moves = []
9
+ end
10
+
11
+ def play(*tile_coordinates)
12
+ tiles = find_tiles(tile_coordinates)
13
+ move = Move.new(self, @current_player, tiles)
14
+ if move.valid?
15
+ move.cover_tiles!
16
+ toggle_current_player
17
+ end
18
+ @moves << move
19
+ @current_player == move.player ? false : true
20
+ end
21
+
22
+ def over?
23
+ uncovered_tiles.empty? || two_passes_in_a_row?
24
+ end
25
+
26
+ def winner
27
+ scores.inject(nil) { |winner, p| p[1] > winner.to_i ? p[0] : winner } if over?
28
+ end
29
+
30
+ def scores
31
+ @players.inject({}) { |scores, player| scores[player_number(player)] = score(player); scores }
32
+ end
33
+
34
+ def board
35
+ tile_rows.collect { |row| row.collect { |tile| tile.to_hash } }
36
+ end
37
+
38
+ def moves
39
+ @moves.collect { |move| move.to_hash }
40
+ end
41
+
42
+ def player_number(player)
43
+ @players.index(player) + 1
44
+ end
45
+
46
+ def score(player)
47
+ covered_tiles(player).count
48
+ end
49
+
50
+ def valid_moves
51
+ @moves.select(&:valid?)
52
+ end
53
+
54
+ def invalid_moves
55
+ @moves.select(&:invalid?)
56
+ end
57
+
58
+ def dictionary
59
+ Linotype::Dictionary.loaded
60
+ end
61
+
62
+ def tile_rows
63
+ @board.tiles
64
+ end
65
+
66
+ def letters
67
+ @board.tiles.flatten.collect(&:letter)
68
+ end
69
+
70
+ def other_player
71
+ @players.index(@current_player) == 0 ? @players[1] : @players[0]
72
+ end
73
+ private :other_player
74
+
75
+ def find_tiles(tile_coordinates)
76
+ puts tile_coordinates.inspect
77
+ return [] if tile_coordinates.empty?
78
+ tile_coordinates.collect do |tile_coordinate|
79
+ tile = tile_rows[tile_coordinate[:row]][tile_coordinate[:column]]
80
+ raise ArgumentError, "The board does not have a tile at that location" unless tile
81
+ tile
82
+ end
83
+ end
84
+ private :find_tiles
85
+
86
+ def toggle_current_player
87
+ @current_player = other_player
88
+ end
89
+ private :toggle_current_player
90
+
91
+ def uncovered_tiles
92
+ covered_tiles(nil)
93
+ end
94
+ private :uncovered_tiles
95
+
96
+ def covered_tiles(player)
97
+ tile_rows.flatten.select { |tile| tile.covered_by == player }
98
+ end
99
+ private :covered_tiles
100
+
101
+ def two_passes_in_a_row?
102
+ valid_moves.count >= 2 && valid_moves[-2,2].select { |move| move.pass? }.count == 2
103
+ end
104
+ private :two_passes_in_a_row?
105
+
106
+ end
107
+ end
@@ -0,0 +1,84 @@
1
+ module Linotype
2
+ class Move
3
+
4
+ attr_reader :game, :player, :invalid_reason
5
+
6
+ def initialize(game, player, tiles)
7
+ @game = game
8
+ @player = player
9
+ @tiles = tiles
10
+ calculate_valid
11
+ end
12
+
13
+ def valid?
14
+ !!@valid
15
+ end
16
+
17
+ def invalid?
18
+ !valid?
19
+ end
20
+
21
+ def word
22
+ @tiles.collect(&:letter).join
23
+ end
24
+
25
+ def pass?
26
+ @tiles.empty?
27
+ end
28
+
29
+ def cover_tiles!
30
+ @tiles.each { |tile| tile.covered_by = @player unless tile.defended? }
31
+ end
32
+
33
+ def to_hash
34
+ {
35
+ player: game.player_number(@player),
36
+ word: word,
37
+ valid: valid?,
38
+ invalid_reason: @invalid_reason,
39
+ player_sequence: game.valid_moves.select { |move| move.player == @player }.index(self).to_i + 1,
40
+ total_sequence: game.valid_moves.index(self).to_i + 1
41
+ }
42
+ end
43
+
44
+ def calculate_valid
45
+ if pass?
46
+ @valid = true
47
+ elsif !uses_game_tiles?
48
+ @invalid_reason = "does not use game tile letters"
49
+ elsif !in_dictionary?
50
+ @invalid_reason = "is not in dictionary"
51
+ elsif !new_word_in_game?
52
+ @invalid_reason = "has been played before"
53
+ elsif !enough_characters?
54
+ @invalid_reason = "is too short"
55
+ elsif prefix_of_previous_word?
56
+ @invalid_reason = "is a prefix of a previously played word"
57
+ else
58
+ @valid = true
59
+ end
60
+ end
61
+
62
+ def in_dictionary?
63
+ game.dictionary.valid?(word)
64
+ end
65
+
66
+ def uses_game_tiles?
67
+ letters = game.letters
68
+ word.each_char { |letter| return false unless letters.delete(letter) }
69
+ end
70
+
71
+ def new_word_in_game?
72
+ !game.valid_moves.collect(&:word).include?(word)
73
+ end
74
+
75
+ def enough_characters?
76
+ word.length >= 2
77
+ end
78
+
79
+ def prefix_of_previous_word?
80
+ game.valid_moves.find { |move| move.word =~ /\A#{word}/ }
81
+ end
82
+
83
+ end
84
+ end
@@ -0,0 +1,5 @@
1
+ module Linotype
2
+ class Player
3
+
4
+ end
5
+ end
@@ -0,0 +1,63 @@
1
+ module Linotype
2
+ class Tile
3
+
4
+ attr_accessor :covered_by
5
+ attr_reader :letter
6
+
7
+ def initialize(board, letter=random_letter)
8
+ @board = board
9
+ @letter = letter
10
+ end
11
+
12
+ def to_hash
13
+ {
14
+ letter: @letter,
15
+ row: row,
16
+ column: column,
17
+ covered_by: (game.player_number(covered_by) if covered_by),
18
+ defended: defended?
19
+ }
20
+ end
21
+
22
+ def game
23
+ @board.game
24
+ end
25
+
26
+ def random_letter
27
+ ('A'..'Z').to_a[rand(0..25)]
28
+ end
29
+
30
+ def row
31
+ @row ||= @board.row(self)
32
+ end
33
+
34
+ def column
35
+ @column ||= @board.column(self)
36
+ end
37
+
38
+ def previous?(coordinate_type)
39
+ send(coordinate_type) > 0
40
+ end
41
+
42
+ def next?(coordinate_type)
43
+ send(coordinate_type) < @board.send("#{coordinate_type}_count") - 1
44
+ end
45
+
46
+ def adjacent_tiles
47
+ @adjacent_tiles ||= calculate_adjacent_tiles
48
+ end
49
+
50
+ def defended?
51
+ adjacent_tiles.select { |tile| tile.covered_by == covered_by && covered_by }.count == adjacent_tiles.count
52
+ end
53
+
54
+ def calculate_adjacent_tiles
55
+ @adjacent_tiles = []
56
+ @adjacent_tiles << @board.tiles[row - 1][column] if previous?(:row)
57
+ @adjacent_tiles << @board.tiles[row + 1][column] if next?(:row)
58
+ @adjacent_tiles << @board.tiles[row][column - 1] if previous?(:column)
59
+ @adjacent_tiles << @board.tiles[row][column + 1] if next?(:column)
60
+ @adjacent_tiles
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,3 @@
1
+ module Linotype
2
+ VERSION = "0.0.1"
3
+ end
data/lib/linotype.rb ADDED
@@ -0,0 +1,11 @@
1
+ require "linotype/version"
2
+ require "linotype/game"
3
+ require "linotype/board"
4
+ require "linotype/move"
5
+ require "linotype/player"
6
+ require "linotype/tile"
7
+ require "linotype/dictionary/dictionary"
8
+
9
+ module Linotype
10
+
11
+ end
data/linotype.gemspec ADDED
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'linotype/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "linotype"
8
+ gem.version = Linotype::VERSION
9
+ gem.authors = ["Sean Devine"]
10
+ gem.email = ["barelyknown@icloud.com"]
11
+ gem.description = <<-eos
12
+ linotype is a small program that implements that game mechanic of Letterpress for iOS by atebits software http://www.atebits.com/letterpress/ The program was written to support the automation of letterpress gameplay and to power command line or web-based versions of the game. It was inspired by a tweet by Andy Baio about cheating in letterpress. https://twitter.com/waxpancake/statuses/261966416507465728 The game uses the words file comes with Mac OS X, but any word file can be used.
13
+ eos
14
+ gem.summary = %q{Small ruby program that implements the game mechanic of the letterpress iOS game.}
15
+ gem.homepage = "https://github.com/barelyknown/linotype"
16
+
17
+ gem.files = `git ls-files`.split($/)
18
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
19
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
20
+ gem.require_paths = ["lib"]
21
+ end
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: linotype
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Sean Devine
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-10-28 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: ! ' linotype is a small program that implements that game mechanic of
15
+ Letterpress for iOS by atebits software http://www.atebits.com/letterpress/ The
16
+ program was written to support the automation of letterpress gameplay and to power
17
+ command line or web-based versions of the game. It was inspired by a tweet by Andy
18
+ Baio about cheating in letterpress. https://twitter.com/waxpancake/statuses/261966416507465728
19
+ The game uses the words file comes with Mac OS X, but any word file can be used.
20
+
21
+ '
22
+ email:
23
+ - barelyknown@icloud.com
24
+ executables: []
25
+ extensions: []
26
+ extra_rdoc_files: []
27
+ files:
28
+ - .gitignore
29
+ - Gemfile
30
+ - LICENSE.txt
31
+ - README.md
32
+ - Rakefile
33
+ - lib/linotype.rb
34
+ - lib/linotype/board.rb
35
+ - lib/linotype/dictionary/dictionary.rb
36
+ - lib/linotype/dictionary/words.txt
37
+ - lib/linotype/game.rb
38
+ - lib/linotype/move.rb
39
+ - lib/linotype/player.rb
40
+ - lib/linotype/tile.rb
41
+ - lib/linotype/version.rb
42
+ - linotype.gemspec
43
+ homepage: https://github.com/barelyknown/linotype
44
+ licenses: []
45
+ post_install_message:
46
+ rdoc_options: []
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ! '>='
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ requirements: []
62
+ rubyforge_project:
63
+ rubygems_version: 1.8.24
64
+ signing_key:
65
+ specification_version: 3
66
+ summary: Small ruby program that implements the game mechanic of the letterpress iOS
67
+ game.
68
+ test_files: []