Created
January 19, 2010 02:57
-
-
Save rjungemann/280615 to your computer and use it in GitHub Desktop.
Maoiste, a library which plays/mediates the card game, Mao
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# the names that are generated for cards are designed to correlate | |
# with the naming system used for this SVG-formatted deck of cards | |
# http://david.bellot.free.fr/svg-cards/ | |
class Object | |
def blank? | |
self.nil? || (self.respond_to?(:empty?) && self.empty?) | |
end | |
end | |
class Array | |
# return an item from a random spot in the array | |
def pick; self[self.rand_i] end | |
# remove an item from a random spot in the array and return it | |
def pull; self.delete_at(self.rand_i) end | |
# insert an item at a random spot in an array | |
def slip obj; self.insert(self.rand_i, obj) end | |
# shuffle the items in the array to new locations | |
def shuffle! | |
size.downto(1) { |n| push delete_at(rand(n)) } | |
self | |
end | |
# return a valid, randomly chosen index of an array | |
def rand_i; (rand * self.size).floor end | |
end | |
class Card | |
@@valid_numbers = (1..10).map { |i| i.to_s }.to_a + %w[jack queen king] | |
@@valid_suits = %w[club diamond heart spade] | |
@@valid_jokers = %w[black_joker red_joker] | |
def self.deck | |
@@valid_numbers.collect { |number| | |
@@valid_suits.collect { |suit| "#{number}_#{suit}" } | |
}.flatten + @@valid_jokers | |
end | |
def self.parse card | |
if @@valid_jokers.include? card | |
{ :name => card, :suit => "joker" } | |
elsif | |
tuple = card.split("_") | |
raise "Invalid card." if tuple.size != 2 | |
raise "Invalid number." unless @@valid_numbers.include?(tuple.first) | |
raise "Invalid suit." unless @@valid_suits.include?(tuple.last) | |
{ :name => card, :number => tuple.first, :suit => tuple.last, :number_value => @@valid_numbers.index(tuple.first) + 1 } | |
else | |
nil | |
end | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# A simulation of the Game of Mao, including rule mutations. | |
# http://en.wikipedia.org/wiki/Mao_(card_game) | |
# | |
# Maoiste is designed to be played over sockets. The server isn't fully implemented, | |
# but here is a class representing game logic. Unlike the real Game of Mao, the rule | |
# mutators are not as robust. I intend to use jruby's sandbox library, combined with | |
# the gazer gem, to allow users to mutate this class using a reasonably accessible | |
# DSL. Currently mutations can be done by altering instance variables of the Maoiste | |
# class. | |
require "#{File.dirname(__FILE__)}/card.rb" | |
class Maoiste | |
def initialize | |
@deck = Card.deck.shuffle | |
@discard_pile = [] | |
@players = [] | |
@index = 0 | |
@hand_size = 5 | |
@dealer = nil | |
@ended = nil | |
@valid_card_conditions = [ | |
{ :is_joker => lambda { |discarded, played| played[:suit] == "joker" || discarded[:suit] == "joker" }}, | |
{ :is_greater_or_equal => lambda { |discarded, played| played[:number_value] >= discarded[:number_value] }}, | |
{ :is_ace_played_on_king => lambda { |discarded, played| played[:number_value] == 1 && discarded[:number_value] == 13 }} | |
] | |
@increment_index = lambda { |index, players| | |
index += 1 | |
index = 0 if index >= players.size | |
index | |
} | |
end | |
def ended?; @ended end | |
def valid_card? card | |
raise "Last discarded card doesn't exist." if last_discarded_card.blank? | |
raise "Played card doesn't exist." if card.blank? | |
discarded = Card.parse last_discarded_card | |
played = Card.parse card | |
if @valid_card_conditions.to_a.find { |c| c.values.first[discarded, played] == true } | |
card | |
else | |
nil | |
end | |
end | |
def last_discarded_card; @deck.last end | |
def add_player name | |
unless @dealer | |
@dealer = name | |
add_player name | |
else | |
unless(name.blank? || @players.include?(name)) | |
player = { :name => name, :hand => [], :picked_up_hand => false, :messages => [] } | |
@players << player | |
player | |
end | |
end | |
end | |
def remove_player name | |
@players.reject! { |player| player == name } | |
end | |
def find_player name | |
@players.find { |player| player[:name] == name } | |
end | |
def send_message name, message | |
puts %{Sent "#{message.first}" to "#{name}"} | |
pl = find_player name | |
pl[:messages] << (message.dup << Time.now) | |
end | |
def start | |
start_message = ["started", "The game is Mao. play proceeds to the left by either suit or number."] | |
@discard_pile << @deck.pull | |
@players.each { |player| | |
@hand_size.times { player[:hand] << @deck.pull } | |
send_message player[:name], start_message | |
} | |
true | |
end | |
def pick_up_hand name | |
if @discard_pile.size < 1 | |
send_message(name, ["error", "Game hasn't started yet."]) | |
return | |
end | |
pl = find_player(name) | |
send_message(name, ["error", "You arent't playing this round."]) if pl.blank? | |
if(name == @dealer || find_player(@dealer)[:picked_up_hand]) | |
pl[:picked_up_hand] = true | |
send_message(name, ["picked_up_hand", pl[:hand]]) | |
else | |
pl[:hand] << @deck.pull | |
send_message(name, ["penalty", "Picking up cards too soon."]) | |
end | |
pl | |
end | |
def draw_card name | |
if @discard_pile.size < 1 | |
send_message(name, ["error", "Game hasn't started yet."]) | |
return | |
end | |
end_game if @deck.size < 1 | |
pl = find_player(name) | |
send_message(name, ["error", "You arent't playing this round."]) if pl.blank? | |
send_message(name, ["error", "You haven't picked up your hand yet."]) unless pl[:picked_up_hand] | |
send_message(name, ["drew", card]) | |
pl | |
end | |
def play name, card = nil | |
if @discard_pile.size < 1 | |
send_message(name, ["error", "Game hasn't started yet."]) | |
return | |
end | |
pl = find_player(name) | |
send_message(name, ["error", "You aren't playing this round."]) if pl.blank? | |
send_message(name, ["error", "You haven't picked up your hand yet."]) unless pl[:picked_up_hand] | |
if card | |
send_message(name, ["error", "You do not have that card."]) unless pl[:hand].include? card | |
end_game if(pl[:hand].size < 1) | |
pl[:hand].reject! { |c| card == c } | |
@discard_pile << card | |
end_game if(pl[:hand].size < 1) | |
send_message(name, ["played", card]) | |
send_message(name, ["penalty", "Playing an invalid card."]) unless(valid_card? card) | |
else | |
draw_card name | |
end | |
send_message(name, ["penalty", "Playing out of order."]) unless(@players.index(pl) == @index) | |
pl | |
end | |
def end_turn name | |
if @discard_pile.size < 1 | |
send_message(name, ["error", "Game hasn't started yet."]) | |
return | |
end | |
pl = find_player(name) | |
send_message(name, ["error", "You aren't playing this round."]) if pl.blank? | |
send_message(name, ["error", "You haven't picked up your hand yet."]) unless pl[:picked_up_hand] | |
send_message(name, ["error", "It's not currently your turn."]) unless(@players.index(pl) == @index) | |
@increment_index[@index, @players] | |
next_pl = @players[@index] | |
send_message(name, ["ended_turn"]) | |
send_message(next_pl[:name], ["your_turn"]) | |
pl | |
end | |
def end_game | |
@ended = true | |
if @discard_pile.size < 1 | |
send_message(name, ["error", "Game hasn't started yet."]) | |
return | |
end | |
@players.each do |player| | |
if player[:hand].size < 1 | |
send_message(player[:name], ["won", "You won this match."]) | |
else | |
send_message(player[:name], ["lost", "You lost this match."]) | |
end | |
end | |
true | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Not exactly a test in the classical sense of the word. Rather, this method is | |
# a simulation of the game of Mao. The computer will play a match against itself, | |
# very quickly (about ten seconds on my computer). This also illustrates the API. | |
require "#{File.dirname(__FILE__)}/maoiste.rb" | |
class Maoiste | |
def self.test | |
require 'pp' | |
pp m = Maoiste.new | |
pp m.add_player "dealer" | |
pp m.add_player "one" | |
pp m.add_player "two" | |
pp m.start | |
pp m.pick_up_hand "one" # results in "penalty, picking up cards too soon" | |
pp m.pick_up_hand "dealer" | |
pp m.pick_up_hand "one" | |
pp m.pick_up_hand "two" | |
until m.ended? | |
%w[dealer one two].each do |name| | |
break if m.ended? | |
pp m.last_discarded_card | |
pp m.draw_card name | |
break if m.ended? | |
card = (rand > 0.5) ? nil : m.find_player(name)[:hand].pick | |
pp m.play name, card | |
break if m.ended? | |
pp m.end_turn name | |
end | |
end | |
m | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# What the implementation of the "nice day rule" will look like. I need to finish | |
# implementing hooks for when a turn ends, etc. | |
# | |
# The "expectation" methods are used when a particular action is required from a | |
# user, such as saying "have a nice day" when a double of a card is played, or | |
# to "slap" a Jack when a Jack appears. | |
class Maoiste | |
def expect_action action, players | |
@expectations ||= {} | |
@expectations[action] = { :players => players, :fulfillments => [] } | |
end | |
def act name, action | |
@expectations[action][:fulfillments] << name | |
end | |
def end_expectation action, &block | |
yield action, @expectations | |
end | |
end | |
#m.when_card_played do |name, card| | |
# if card == last_discarded_card | |
# @nice_day_level = @nice_day_level + 1 || 1 | |
# @nice_day_action = "have_#{'very_' * (@nice_day_level - 1)}nice_day" | |
# | |
# expect_action @nice_day_action, *@players | |
# else | |
# @nice_day_level = 0 | |
# end | |
#end | |
# | |
#m.when_turn_ended do |name| | |
# end_expectation(@nice_day_action) do |action, expectations| | |
# @nice_day_action = nil | |
# | |
# @players.each do |player| | |
# unless expectations[:fulfillments].include? player[:name] | |
# send_message(name, ["penalty", "Failure to have a #{'very ' * (@nice_day_level - 1)}nice day."]) | |
# draw_card player[:name] | |
# end | |
# end | |
# end | |
#end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment