linotype 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +104 -0
- data/Rakefile +1 -0
- data/lib/linotype/board.rb +37 -0
- data/lib/linotype/dictionary/dictionary.rb +27 -0
- data/lib/linotype/dictionary/words.txt +235886 -0
- data/lib/linotype/game.rb +107 -0
- data/lib/linotype/move.rb +84 -0
- data/lib/linotype/player.rb +5 -0
- data/lib/linotype/tile.rb +63 -0
- data/lib/linotype/version.rb +3 -0
- data/lib/linotype.rb +11 -0
- data/linotype.gemspec +21 -0
- metadata +68 -0
@@ -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,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
|
data/lib/linotype.rb
ADDED
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: []
|