Automating the Online Set Card Game

Automating the Online Set Card Game

The Game

There’s a card game here that’s pretty fun to play. The challenge is to find all 6 sets of cards that make up a “set”. Rules can be found inthis pdf.

Each day they have a puzzle that you can solve to find the 6 sets.

If you sign up for a free account, you can submit your name into a weekly drawing for a Set card game. Like a real one you can touch with your hands and stuff.

I like free stuff. And I like automating stuff!

Manual First, Then Automate

Before trying to automate anything though, it’s a good idea to run through the process manually.

See if you can find some sets. What happens when you find a set vs. what happens when the cards you pick aren’t actually a set? That’ll be useful info to know about later.

Once you know how the game works, then it’s worth trying to automate. Let’s give it a shot.

The Toolbox

The tool of choice is Watir, which is a Ruby library for interacting with web pages. If you’re familiar with Selenium, you’re already ahead–all Watir does is put some test tools in with the stuff that interacts with elements on the webpage.

Boilermaker

Let’s start by putting in the code to pull the appropriate gems at the top of the script:

require "rubygems"
require "watir-webdriver"

And if you haven’t installed these gems yet, just install watir from the command line by typing “gem install watir”. All the other dependencies related to Watir will be installed too. rubygems is part of a standard Ruby install.

Next, we need some boilerplate for opening up a browser and navigating to a url:

def setup
   client = Selenium::WebDriver::Remote::Http::Default.new
   client.timeout = 600
   # set up the $b component with the info here.
   $b = Watir::Browser.new :ff, :http_client => client
   $timeout_length = 30
end

def go_to_url(url)
   load_link($timeout_length){ $b.goto url }
end

def load_link(waittime)
   begin
      Timeout::timeout(waittime) do
      yield
      end
   rescue Timeout::Error => e
      log("Page load timed out: #{e}")
      retry
   end
end

For the final bit of boilerplate, we need something to close out the browser when we’re done, so we don’t have browsers tarting up the taskbar:

def teardown
   $b.close
end

“Let’s do something a little more fun. How about… combat training.”

Ok, now that that’s out of the way, let’s open up a browser and go to the site:

setup
go_to_url "https://www.puzzles.setgame.com/puzzle/set.htm"

You’ll be presented with a playing board like this: 

Each of the cards are clickable elements, and will set the checkbox underneath. So next we need to look at the DOM and see if there’s any info in there we can use.

Let’s look at the first card’s DOM contents by doing a right-click followed by Inspect Element in the browser:

Ok, now let’s look at the second one: 

And then the third one: 

Ok I think we’re starting to see a pattern here–I bet the 12th card in the bottom right corner has the parent element href attribute set to “javascript:board.cardClicked(12)”. Let’s see if that theory holds: 

Yep. So that’s good. That means we have something logical to tag onto when trying to figure out what card to click.

It also means we can try a brute-force approach to figuring out what cards make up a set and which don’t.

So let’s make a few more methods to help clean up the code we know we’re going to write.

We know for sure we’ll have to click on a card, so let’s write one that clicks the card based on that href attribute:

def click_card(value)
   $b.element(:xpath => '//a[@href="javascript:board.cardClicked(' + value + ')"]').when_present.click
end

We also know from playing the game manually that when you click 3 cards, you’ll get a popup that either says “GREAT!” for when you get a set, or something else for when we didn’t actually get a set. So let’s put 3 more methods in here to help readability later–one for waiting for the inevitable alert:

def wait_for_alert
   $b.alert.wait_until_present
end

One to check the alert to see if it says “GREAT” indicating you got a set:

def you_found_a_set
   if $b.alert.text.include?("GREAT")
      return true
   else
      return false
   end
end

And then one to close the alert when done, by clicking the OK button:

def close_alert
   $b.alert.when_present.ok
end

Then, at the end of a game, there will be a place for you to enter your username, and also a banner on the next page that says “Congratulations!” so you know your info’s been entered properly. Let’s make those real quick:

def enter_user_id(user)
   # use the select-all-text-and-delete method for entering
   # the username.
   $b.text_field(:xpath => "https://input[@id='edit-submitted-user-id']").when_present.click
   $b.text_field(:xpath => "https://input[@id='edit-submitted-user-id']").when_present.send_keys [:control, 'a']
   $b.text_field(:xpath => "https://input[@id='edit-submitted-user-id']").when_present.send_keys [:delete]
   $b.text_field(:xpath => "https://input[@id='edit-submitted-user-id']").when_present.send_keys(user)
   $b.text_field(:xpath => "https://input[@id='edit-submitted-user-id']").when_present.send_keys [:enter]
end

def wait_for_congratulations
   $b.element(:xpath => "https://h1[contains(.,'Congratulations!')]").wait_until_present
end

Awesome. Now for the challenging part.

We’ll need an array to hold the sets that we find. This will come in handy when we want to find out how to stop trying to find sets, and finish up today’s puzzle.

We’ll put this into a global variable, signified by the dollar sign:

$sets = []

Now, as I said earlier, knowing that there’s a number 1 through 12 assigned to each card allows us to brute force the solution.

If you wanted to solve this more elegantly by having your script intelligently find what cards are sets and what aren’t, that’d be a great exercise.

But yeeeeeeeeah, I’m not gonna do that here.

In mathematic terms, what we’re wanting to do is try every possible combination of 3-sets from the values 1 through 12.

It’s a combination as opposed to a permutation, because we can’t pick the same card multiple times. So for example, 1,2,3 is a combination but 1,1,2 is a permutation.

Fortunately, Ruby offers a way to give you all possible combinations for an array of values. So let’s make an array of values from 1 to 12:

a = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"]

And then let’s make a loop to go through every possible combination of values:

a.combination(3).each do |combination|
   *:??? magic happens here! ???:*
end

Now we’re gonna make some magic.

For each combination we’ll get 3 values. We’ll click those cards, but we also want to keep track of what cards were clicked, in case it was a set.

Let’s write some code to loop through those 3 values and click the cards represented by them:

a.combination(3).each do |combination|
   $cards = []
   combination.each do |value|
      click_card(value)
      $cards.push(value)
   end
end

We’re not done yet, now we need to tell the script what to do once a set is clicked. This goes back to the methods we wrote earlier, to tell what to do with that popup that’s going to show up:

a.combination(3).each do |combination|
   $cards = []
   combination.each do |value|
      click_card(value)
      $cards.push(value)
   end
   wait_for_alert
   if you_found_a_set
      $sets.push($cards)
   end
   close_alert
end

Finally, we add some code at the end to tell this loop when to stop–you don’t want to keep trying to click cards once you’ve found 6 sets:

a.combination(3).each do |combination|
   $cards = []
   combination.each do |value|
      click_card(value)
      $cards.push(value)
   end
   wait_for_alert
   if you_found_a_set
      $sets.push($cards)
   end
   close_alert
   if $sets.length == 6
      enter_user_id("supahotfire")
      wait_for_congratulations
      break
   end
end

If we run the whole thing, eventually we’ll be able to enter the username for an account that was created before, and then wait for the congratulations banner to signify that the username was entered and processed:

 

All done! Now to wait for my free card game... maybe. 

Hope You Enjoyed it! Next Challenges…

Was that fun? I think it’s cool that not just software testing can be done, using the same kind of tools.

What are some improvements you can make? How can you make it more efficient? What if you wanted to do it for multiple user accounts?

Hmm… intriguing. Drop a comment and share what you think!

Michael "Fritz" Fritzius

Podcasting with a Mission

9 年

Well that's terrible. The formatting's all gone. Here's a link to the original until I can fix this post: https://testzius.wordpress.com/2015/12/05/automating-the-online-set-card-game/ sry :(

回复

要查看或添加评论,请登录

Michael "Fritz" Fritzius的更多文章

  • Get Booked on More Podcasts Without the Hassle

    Get Booked on More Podcasts Without the Hassle

    Hey, are you looking to get booked on more podcasts? The last couple months, I’ve seen a rise in people asking me how…

    6 条评论
  • How to Get More Podcast Hosts Saying "Yes" to You: The Right Media Kit

    How to Get More Podcast Hosts Saying "Yes" to You: The Right Media Kit

    Years ago when I made my first Media Kit for podcasting, I thought, "Wow, what an easy way for hosts to find out…

  • How to Become an Awesome Podcast Guest

    How to Become an Awesome Podcast Guest

    A few months ago, there was a sudden uptick in people asking advice about how to be a better podcast guest. As someone…

    1 条评论
  • Pivoting - New Networking Event

    Pivoting - New Networking Event

    A few months ago I started a journey that I didn't realize was going to change me as a person: I decided to host an…

    6 条评论
  • How 500 Die-Hard Podcast Fans Can Support Your Business

    How 500 Die-Hard Podcast Fans Can Support Your Business

    Is getting a massive listener count and attracting fat sponsorships the only way that a podcast can support your…

    6 条评论
  • Busting 4 Common Myths of Test Automation

    Busting 4 Common Myths of Test Automation

    It's been around for decades but the fast paced life of software has led to some myths being perpetuated. about test…

    17 条评论
  • The "One Man Army" Approach of our Services

    The "One Man Army" Approach of our Services

    Not every project needs a bigger team. But you get one anyway for free.

    2 条评论
  • Why You Don't Really Need a Hero

    Why You Don't Really Need a Hero

    Once upon a time, this one place had some test automation. A couple of wizard-level guys built it and maintained it.

    27 条评论
  • How To Break Through the 30K Barrier

    How To Break Through the 30K Barrier

    Hey I heard you just hit the 30K connection cap. Congrats! But I bet it's annoying to be told that you can't have any…

    17 条评论
  • Automating the NSA Cryptochallenge

    Automating the NSA Cryptochallenge

    Howdy folks, Normally I don't have a blatant redirect from here over to my blog, but I find that the way LinkedIn…

社区洞察

其他会员也浏览了