15
\$\begingroup\$

API Easter egg hunt!

There's an API at http://easter_egg_hunt.andrewfaraday.com which will provide a special Easter egg hunt, just for you...

You can see the API docs at the above address, or try it from here.

The API:

All calls to this API are GET requests, which will return a JSON string.

These examples are in a 5x5 garden, for illustration only. The API will actually operate on a 100x100 garden (from indexes 1 to 100)

/new_game

Internally, the API produces a garden, and hides an egg in it.

In this example the egg is at 4, 4

+----------+
|          |
|          |
|          |
|          |
|   E      |
|          |
|          |
|          |
|          |
|          |
+----------+

Call

/new_game

Returns

{game_id: 'abcde'}

/guess/:game_id/:x/:y

The API looks in the garden and tells you how close you are.

If you guess 2 across and 8 down, the garden looks like this

+----------+
|          |
|          |
|          |
|          |
|   E      |
|          |
|          |
| g        |
|          |
|          |
+----------+

Call

/guess/abcde/2/8

Returns

{x: 'higher', y: 'lower'}

This means: * Your x is too low (the egg position is higher) * Your y is too high (The egg position is lower)

Correct call:

/guess/abcde/4/4

Returns

{x: 'right', y: 'right', turns: 10}

The Rules

Write a program to find the Easter egg with an API.

  • Use any language.
  • Try to write concise, but readable code.
  • Your program MUST call '/new_game' every time, and use the game_id returned in all 'guess' calls. No peeking at the garden!
  • Try to consistently finish the game with the smallest number of calls possible.
  • This is not code golf.

Competetive answer?

To be in with a chance of winning here's what we need to know:

  • What code are you using (In your answer, or a github link if it's bigger than you like to put in an answer).
  • Run your code 10 times, and record the game_id and score each time.

-game_id-: -score-

e.g.

abbbbbbb: 10

abbbbbdd: 5

(Note: The game_ids are required to verify a result)

The score will be calculated thus:

  • The highest and lowest two results will be ignored.
  • The remaining 6 scores will be added together.
  • This is your score.
  • Lowest score wins.

Addendum

Tl,dr: The app is a little shoddy.

The API was written in the space of about 90 minutes, and is deployed on a Raspberry Pi 2. Please be gentle with my API server. If you DDOS this poor thing you could spoil it for everyone.

Also, it's implemented with NOIP to simulate a static IP address, but sometimes this will drop out for a little while. That's what I get for using for zero-budget web hosting.

Just for giggles, here's a simple statistical analysis of games played... http://easter_egg_hunt.andrewfaraday.com/stats

\$\endgroup\$
  • \$\begingroup\$ Comments are not for extended discussion; this conversation has been moved to chat. \$\endgroup\$ – Dennis Mar 31 '18 at 15:39
3
\$\begingroup\$

APL+WIN

Works by starting in the centre and converges on the location by setting the upper or lower limit to the last value and adding or subtracting half the difference to the appropriate limit to the last guess on each iteration.

API                                                                               
id←¯20↑¯2↓GetUrl 'http://easter_egg_hunt.andrewfaraday.com/new_game'              
xh←yh←100 ⋄ xl←yl←0 ⋄ x←50 ⋄ y←50 ⋄ c←0                                           
:repeat                                                                           
    xy←GetUrl 'http://easter_egg_hunt.andrewfaraday.com/guess/',id,'/',(⍕x),'/',⍕y
    xy←(('higher'⍷xy)+(¯1×'lower'⍷xy)+2×'right'⍷xy)~0                             
    :if xy[1]=1 ⋄ xl←x ⋄ x←x+⌈(xh-x)÷2 ⋄ :endif                                   
    :if xy[1]=¯1 ⋄ xh←x ⋄ x←x-⌈(x-xl)÷2 ⋄ :endif                                  
    :if xy[2]=1 ⋄ yl←y ⋄ y←y+⌈(yh-y)÷2 ⋄ :endif                                   
    :if xy[2]=¯1 ⋄ yh←y ⋄ y←y-⌈(y-yl)÷2 ⋄ :endif                                  
    c←c+1                                                                         
:until 4=+/2↑xy                                                                   
'id:',id,' x:',(⍕x),' y:',(⍕y),' count:',⍕c 

This function uses the following function to do the API calls:

r←GetUrl url                                     
⎕wself←'HTTP' ⎕wi 'Create' 'MSXML2.ServerXMLHTTP'
⎕wi 'XOpen' 'GET' url 0                          
⎕wi 'XSend'                                      
r←⎕wi 'xresponseText'

The results for 10 trys were:

id:rbgprkxrqzzhwdfsbszn x:36 y:52 count:7      
id:nmpcxdqsdzhgrbtlcpbp x:35 y:49 count:6      
id:qqnsbpwnlbptxxblywnz x:99 y:22 count:6      
id:nsytnvcgnsyrgzvjcysc x:45 y:28 count:6      
id:yfkpfhphjpqxtqnwpmhv x:95 y:40 count:7      
id:kxhszzrhxqlnvwvwjgnm x:49 y:6 count:6       
id:rwnwfgdpzcjpzzfmgcfn x:93 y:34 count:7      
id:tcvhtpqlfrwngybsyzqh x:95 y:94 count:6      
id:pmlmqnprwcjggjfhttmy x:20 y:41 count:6      
id:kpsmmhfhxxrrlvbbgzkv x:9 y:28 count:5                   
\$\endgroup\$
2
\$\begingroup\$

Ruby (+JSON, HTTParty) - Score: 40 (6+7+7+7+7+6)

This was a fun challenge. I used binary search to find the egg and got these results:

[{:x=>34, :y=>17, :game_id=>"mgpbmdqbnklcqrdjpyrr", :count=>7},
 {:x=>99, :y=>17, :game_id=>"mhrsqfzmrrlcqxtcfgnw", :count=>7},
 {:x=>23, :y=>86, :game_id=>"zgmsrjpqvdtmqmmglstn", :count=>6},
 {:x=>24, :y=>55, :game_id=>"vkpjffyyltplztwhdsft", :count=>7},
 {:x=>12, :y=>94, :game_id=>"pxrzjvqfjrjsptvtvnfw", :count=>4},
 {:x=>83, :y=>59, :game_id=>"bdxljxkcnqmsqgnvggql", :count=>7},
 {:x=>45, :y=>40, :game_id=>"mqrsbrhbldcqwgbnmymc", :count=>7},
 {:x=>13, :y=> 9, :game_id=>"bphxkdgfcyyrvwxnfvkx", :count=>6},
 {:x=> 8, :y=>80, :game_id=>"qzdstksdwnwrhxqrczpc", :count=>7},
 {:x=>56, :y=>92, :game_id=>"ypqkfvmvwrcvvmjccvxg", :count=>6}]

Here's the code:

require 'rspec/autorun'
require 'json'
require 'httparty'
require 'pp'

GuessResult = Struct.new :x, :y, :count

class FakeGame
  def initialize(x=random_num, y=random_num)
    @x = x
    @y = y
    @count = 0
  end

  @@results = [:right, :higher, :lower]

  def guess(x, y)
    @count += 1
    GuessResult.new(@@results[@x <=> x], @@results[@y <=> y], @count)
  end

  def id
    :fake
  end

  def random_num
    rand(100) + 1
  end
end

class RealGame
  def initialize
    response = HTTParty.get('http://easter_egg_hunt.andrewfaraday.com/new_game')
    j = JSON.parse(response.body)
    @id = j['game_id']
  end

  def guess(x, y)
    response = HTTParty.get("http://easter_egg_hunt.andrewfaraday.com/guess/#{id}/#{x}/#{y}")
    j = JSON.parse(response.body)
    x_result = j['x'].to_sym
    y_result = j['y'].to_sym
    count = (j['turns']||0).to_i
    GuessResult.new(x_result, y_result, count)
  end

  def id
    @id
  end
end


class BinarySearch
  def initialize(min, max)
    @min = min
    @max = max
    @guessed = false
    update_next_guess
  end

  attr_reader :next_guess, :guessed

  def go(result)
    return if @guessed
    case result
    when :right
      @guessed = true
    when :lower
      @max = @next_guess - 1
      update_next_guess
    when :higher
      @min = @next_guess + 1
      update_next_guess
    end
  end

  private

  def update_next_guess
    @next_guess = (@max + @min) / 2
  end

end

def play(game)
  x_search = BinarySearch.new(1, 100)
  y_search = BinarySearch.new(1, 100)

  until x_search.guessed && y_search.guessed
    puts ?.
    result = game.guess(x_search.next_guess, y_search.next_guess)
    x_search.go(result.x)
    y_search.go(result.y)
  end

  {
    x: x_search.next_guess,
    y: y_search.next_guess,
    game_id: game.id,
    count: result.count
  }
end


def game_controller(game_constructor, game_count)
  (1..game_count).map do |i|
    game = game_constructor.call
    puts "Starting game #{game.id}..."
    play(game)
  end
end


def main
  # pp game_controller(->{ FakeGame.new }, 10)
  pp game_controller(->{ RealGame.new }, 10)
end

main


# tests

describe :FakeGame do

  it "returns right results" do
    game = FakeGame.new 4, 4

    result = game.guess(2, 5)
    expect(result.x).to eql :higher
    expect(result.y).to eql :lower
    expect(result.count).to eql 1

    result = game.guess(5, 3)
    expect(result.x).to eql :lower
    expect(result.y).to eql :higher
    expect(result.count).to eql 2

    result = game.guess(4, 4)
    expect(result.x).to eql :right
    expect(result.y).to eql :right
    expect(result.count).to eql 3

  end

end

describe :binary_search do
  let(:search) { BinarySearch.new 1, 100 }

  it "makes optimal guesses" do
    # aiming for 34
    expect(search.next_guess).to eql 50
    expect(search.guessed).to be_falsey
    search.go(:lower)
    expect(search.next_guess).to eql 25
    search.go(:higher)
    expect(search.next_guess).to eql 37
    search.go(:lower)
    expect(search.next_guess).to eql 31
    search.go(:higher)
    expect(search.next_guess).to eql 34
    search.go(:right)
    expect(search.next_guess).to eql 34
    expect(search.guessed).to be_truthy
  end

end

describe :fake_game do

  it "correctly responds to guesses" do
    game = FakeGame.new(34, 77)
    result = play(game)
    expect(result.y).to eql :lower
    expect(result.count).to eql 1

    result = game.guess(5, 3)
    expect(result.x).to eql :lower
    expect(result.y).to eql :higher
    expect(result.count).to eql 2

    result = game.guess(4, 4)
    expect(result.x).to eql :right
    expect(result.y).to eql :right
    expect(result.count).to eql 3

  end

end

describe '#play' do

  it "guesses correctly" do
    game = FakeGame.new(34, 77)
    result = play(game)
    expect(result[:x]).to eql 34
    expect(result[:y]).to eql 77
    expect(result[:count]).to eql 7
    expect(result[:game_id]).to eql :fake
  end

end
\$\endgroup\$
  • \$\begingroup\$ Looks like I've messed up, here. easter_egg_hunt.andrewfaraday.com/stats states that only 12 games have been completed in the history of the app. I'm sure there was at least 6 completed yesterday. Although 160 is the longest. I'll have a look into it. \$\endgroup\$ – AJFaraday Mar 29 '18 at 9:12
  • \$\begingroup\$ The issue appears to be in RealGame#guess, where you've hardcoded the game with game_id "bswtdwfdjypdtfyqxbyz". Replace this with #{@id} to get a more accurate result set. \$\endgroup\$ – AJFaraday Mar 29 '18 at 9:17
  • \$\begingroup\$ Actually, I corrected that one line and your result set is pretty good. Rather consistently 6's and 7's. Nice work :D \$\endgroup\$ – AJFaraday Mar 29 '18 at 9:21
  • \$\begingroup\$ @AJFaraday Oops. My bad! Thanks for fixing! And thanks for making this fun challenge! \$\endgroup\$ – Cristian Lupascu Mar 29 '18 at 9:55
  • \$\begingroup\$ You had me worried for a minute, there ;) \$\endgroup\$ – AJFaraday Mar 29 '18 at 9:56
2
\$\begingroup\$

Python 3 (+requests), 39 points

http://easter_egg_hunt.andrewfaraday.com/guess/dbxqfgldhryxymljthkx/13/82
{"turns": 7, "url": "http://easter_egg_hunt.andrewfaraday.com/surprise", "x": "right", "y": "right"}
http://easter_egg_hunt.andrewfaraday.com/guess/kfmgrdqlyxfknbgycfwm/6/52
{"turns": 7, "url": "http://easter_egg_hunt.andrewfaraday.com/surprise", "x": "right", "y": "right"}
http://easter_egg_hunt.andrewfaraday.com/guess/gnykwddprlfwwkrybkmc/34/91
{"turns": 6, "url": "http://easter_egg_hunt.andrewfaraday.com/surprise", "x": "right", "y": "right"}
http://easter_egg_hunt.andrewfaraday.com/guess/xhwrqdgtdyrwrvdqqcpk/92/54
{"turns": 7, "url": "http://easter_egg_hunt.andrewfaraday.com/surprise", "x": "right", "y": "right"}
http://easter_egg_hunt.andrewfaraday.com/guess/hmbgrnxjfgqcxhbfkztm/44/48
{"turns": 7, "url": "http://easter_egg_hunt.andrewfaraday.com/surprise", "x": "right", "y": "right"}
http://easter_egg_hunt.andrewfaraday.com/guess/mhbchhbkppqqyxzqvrnb/62/38
{"turns": 7, "url": "http://easter_egg_hunt.andrewfaraday.com/surprise", "x": "right", "y": "right"}
http://easter_egg_hunt.andrewfaraday.com/guess/pbrkghynqybmkmctncmr/73/25
{"turns": 6, "url": "http://easter_egg_hunt.andrewfaraday.com/surprise", "x": "right", "y": "right"}
http://easter_egg_hunt.andrewfaraday.com/guess/sspxcsfblrnmhflgtggn/89/73
{"turns": 6, "url": "http://easter_egg_hunt.andrewfaraday.com/surprise", "x": "right", "y": "right"}
http://easter_egg_hunt.andrewfaraday.com/guess/rlvdstmpsthktzkqbynn/4/71
{"turns": 6, "url": "http://easter_egg_hunt.andrewfaraday.com/surprise", "x": "right", "y": "right"}
http://easter_egg_hunt.andrewfaraday.com/guess/wknwwcrdrmjsqxnqbvhm/50/67
{"turns": 6, "url": "http://easter_egg_hunt.andrewfaraday.com/surprise", "x": "right", "y": "right"}

Score 39 (7+7+7+6+6+6 - 7-7-6-6)

Source:

import requests
import json
url='http://easter_egg_hunt.andrewfaraday.com/guess/{id}/{x}/{y}'
start='http://easter_egg_hunt.andrewfaraday.com/new_game'
gid = requests.get(start).json()['game_id']
x=y=50
step=25
lx=lr=''
def get_new(old,value):
        if value == 'higher':
                return old+step
        elif value == 'lower':
                return old-step
        else:#right
                return old
while True:
        res = requests.get(url.format(id=gid,x=x,y=y)).json()
        if res['x'] == 'right' and res['y'] == 'right':
                print(url.format(id=gid,x=x,y=y))
                print(json.dumps(res, sort_keys=True))
                break
        x=get_new(x,res['x'])
        y=get_new(y,res['y'])
        step=step // 2 or 1
\$\endgroup\$
  • \$\begingroup\$ Just noticed there is no gameid in the result, I will rerun it and add the game id's in a bit \$\endgroup\$ – Rick Rongen Mar 29 '18 at 10:35
  • \$\begingroup\$ You've also over-scored yourself. We're discounting the highest two and the lowest two results. Leaving only 6 to sum... You could always put the score in the title, too. \$\endgroup\$ – AJFaraday Mar 29 '18 at 12:21
  • \$\begingroup\$ Oh I misread, I thought only to remove the first and last one. I will modify it then :) \$\endgroup\$ – Rick Rongen Mar 29 '18 at 14:30
2
\$\begingroup\$

PHP

<?php

error_reporting(E_ALL);

$url = 'http://easter_egg_hunt.andrewfaraday.com';
extract(json_decode(file_get_contents("$url/new_game"), true));

$i = $j = 51;
$step = 50;
$right = false;

while(!$right) {
  extract(json_decode(file_get_contents(sprintf("$url/guess/$game_id/%d/%d", $i, $j)), true));
  $lower = -$higher = $step /= 2;
  $i += $$x;
  $j += $$y;
  $right = !($$x || $$y);
}

printf('{game_id: %s; x: %2d; y: %2d; turns: %d}', $game_id, $i, $j, $turns);

Using file_get_contents and json_decode.

As far as I can tell, an optimal strategy needs at most 7 moves, with an average of 5.8 moves on one axis, and an average of 6.4786 moves on two axes.


Sample of 10

{game_id: pfmyldcsltlbtmcfjtqr; x: 76; y: 51; turns: 6}
{game_id: jnmshsfvstcmksdcdrnj; x: 40; y:  5; turns: 7}
{game_id: wsrptrymycxjfxwvdvlh; x: 55; y: 35; turns: 7}
{game_id: fxpxtqwnxstwcxbsqtwc; x: 73; y: 93; turns: 6}
{game_id: zppntvjxnjpnlmpfzcfj; x: 71; y: 76; turns: 6}
{game_id: fzvlxqgrxcvtbbydgrpj; x: 48; y: 51; turns: 6}
{game_id: hqytpzjxkfhqhhwwfryd; x: 23; y: 87; turns: 6}
{game_id: ldsbfgcqbqpxgdhtkxsm; x: 90; y: 81; turns: 5}
{game_id: shypwsmjljyqdvwcwnxv; x: 19; y: 57; turns: 7}
{game_id: bsfrhhrvmpqfvyhjxcwh; x: 82; y: 85; turns: 6}

Score: 6 + 6 + 6 + 6 + 6 + 7 = 37


Golfed, 245 bytes

<?$e="extract(json_decode(file_get_contents('http://easter_egg_hunt.andrewfaraday.com";eval("$e/new_game".$f="'),1));");
for($i=$j=$h=50;!$url;$l=-$h/=2)eval("$e/guess/$game_id/".-~($i+=$$x[0]).'/'.-~($j+=$$y[0]).$f);
echo$game_id,~$i,~$j,-$turns;

Sample Output

$ php egg-hunt.php
hgzqmqyrznglsdwfwcft-9-86-7
\$\endgroup\$
2
\$\begingroup\$

Haskell, Total Score 66 40

(Edit: Missed the part about dropping highest and lowest results in scoring)

I'm just using binary search. Not sure if there's a way to get better than 6.5 per try on average without cheating (I suppose I could just keep running sets of 10 until I get a better score, but where's the fun in that?).

Ah, by the way very fun challenge. The idea is simple enough to be inclusive of everyone, and I don't usually deal with web APIs in my day job and I was just itching for an excuse to try out servant, so thanks for that :) Probably not so great if you're hoping for competition, but I think challenges like this could be used to gather code samples for beginners and things like that.

Executable script (runs one game and reports the gameId and score):

#!/usr/bin/env stack
{- stack
   --resolver lts-11.2
   script
   --package servant
   --package servant-client
   --package http-client
   --package aeson
   --package text
-}

-- the above comments allow this to be run as a script if haskell-stack is installed.
-- Actual source starts here.
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}

module Main where

import Data.Aeson
import Servant.API
import Servant.Client
import qualified Network.HTTP.Client as HttpClient
import Data.Proxy
import Data.Text(Text)
import Text.Printf
import System.IO(stderr)

newtype GameID = GameID Text deriving (PrintfArg, ToHttpApiData)

instance FromJSON GameID where
  parseJSON = withObject "GameID" $ \o ->
    fmap GameID (o .: "game_id")

data Accuracy = Lower | Higher | Correct

readAccuracy :: Text -> Accuracy
readAccuracy t
  | t == "lower" = Lower
  | t == "higher" = Higher
  | t == "right" = Correct
  | otherwise = error $ printf "Unexpected accuracy text: \"%s\"" t

data GuessResult = GuessResult { xAccuracy :: Accuracy, yAccuracy :: Accuracy, turnCount :: Maybe Int }

instance FromJSON GuessResult where
  parseJSON = withObject "GuessResult" $ \o ->
    GuessResult
      <$> fmap readAccuracy (o .: "x")
      <*> fmap readAccuracy (o .: "y")
      <*> o .:? "turns"

type EggAPI =    "new_game" :> Get '[JSON] GameID
            :<|> "guess"
              :> Capture "game_id" GameID
              :> Capture "x" Int
              :> Capture "y" Int
              :> Get '[JSON] GuessResult

getNewGame :: ClientM GameID
makeGuess :: GameID -> Int -> Int -> ClientM GuessResult
getNewGame :<|> makeGuess = client (Proxy :: Proxy EggAPI)

data CoordinateRange = CoordinateRange { lowerBound :: Int, higherBound :: Int }

middleOfRange :: CoordinateRange -> Int
middleOfRange rng = lowerBound rng + (higherBound rng - lowerBound rng) `div` 2

adjustCoordinateRange :: Accuracy -> CoordinateRange -> CoordinateRange
adjustCoordinateRange Lower rng = CoordinateRange (lowerBound rng) (middleOfRange rng)
adjustCoordinateRange Higher rng = CoordinateRange (middleOfRange rng) (higherBound rng)
adjustCoordinateRange Correct rng = rng

searchForEggs :: ClientM (GameID, Int)
searchForEggs = do
  game <- getNewGame
  let initialRange = CoordinateRange 1 100
  score <- loop game initialRange initialRange
  return (game, score) where
    loop gId xRange yRange = do
      guessResult <- makeGuess gId (middleOfRange xRange) (middleOfRange yRange)
      let newXRange = adjustCoordinateRange (xAccuracy guessResult) xRange
          newYRange = adjustCoordinateRange (yAccuracy guessResult) yRange
      maybe (loop gId newXRange newYRange) return $ turnCount guessResult

main :: IO ()
main = do
  manager' <- HttpClient.newManager HttpClient.defaultManagerSettings
  let clientEnv = mkClientEnv manager' (BaseUrl Http "easter_egg_hunt.andrewfaraday.com" 80 "")
  result <- runClientM searchForEggs clientEnv
  case result of
    Left err -> hPrintf stderr  "Error: %s\n" (show err)
    Right (game, score) -> printf "GameID: %s | Score: %d\n" game score

Results

GameID               |  Score
---------------------+-------
fdcbwwxkvhkfskqlpgnh |  7
cdgjnksfnrhgjjsdbnhd |  7
lbjjqgkvfzzprnrxcpsx |  6
rtbngkdlwdfmhdyggnjd |  6
rcphvxzzgblfnzxdqlyh |  6
gyfjbjmplkrfnqjptygl |  7
bkdnbqhsbhwwvgtcfhjb |  6
knjdxdmvttwgltjdpvtv |  7
zqpstnhjgsykkwxnxcbv |  7
rccpmsbfxqvsmzxckhcs |  7
\$\endgroup\$
2
\$\begingroup\$

JavaScript, 35 points

Posting ungolfed code does not sit well with me! :D

(async _=>{
    url=`https://crossorigin.me/http://easter_egg_hunt.andrewfaraday.com/`
    promise=await fetch(url+`new_game`)
    json=await promise.json()
    id=json.game_id
    max={x:100,y:100}
    min={x:0,y:0}
    (guess=async (x,y)=>{
        promise=await fetch(url+`guess/${id}/${x|=0}/${y|=0}`)
        json=await promise.json()
        turns=json.turns
        if(turns)
            console.log(`{game:"${id}",turns:${turns},x:${x},y:${y}}`)
        else{
            switch(json.x){
                case`higher`:
                    min.x=x
                    x+=max.x
                    x/=2
                    break
                case`lower`:
                    max.x=x
                    x+=min.x
                    x/=2
            }
            switch(json.y){
                case`higher`:
                    min.y=y
                    y+=max.y
                    y/=2
                    break
                case`lower`:
                    max.y=y
                    y+=min.y
                    y/=2
            }
            guess(x,y)
        }
    })(50,50)
})()

Scoring: 5+6+6+6+6+6=35

Got very lucky, pulling that score of 3 on my last test before posting!

{game:"bjzkjzxwmksmbsbxtdzp",turns:3,x:75,y:12}
{game:"bvmhssnmzhlnykgxdkww",turns:5,x:93,y:71}
{game:"mcydbttxhcxwqymksgbg",turns:5,x:71,y:37}
{game:"xdynxrkxgsyltsfrqzll",turns:6,x:54,y:88}
{game:"wjdkclsqksnvdnwbspxq",turns:6,x:90,y:13}
{game:"mgvlssfgjcgtylwqpvhq",turns:6,x:26,y:68}
{game:"rgjvbkrlzqvpdfphqxtq",turns:6,x:19,y:81}
{game:"hgrscvfzgrkzzjvkjjwb",turns:6,x:41,y:19}
{game:"lrfrblmmkggghntshnkj",turns:7,x:86,y:32}
{game:"ldsndvjsbvgvbhbtfckp",turns:7,x:24,y:7}

Try it

Using a somewhat golfed version of the code above!

(async url=>(
    json=await(await fetch(url+`new_game`)).json(),
    id=json.game_id,
    max={x:100,y:100},
    min={x:0,y:0},
    (g=async x=>(
        json=await(await fetch(url+`guess/${id}/${x|=0}/${y|=0}`)).json(),
        x+=json.x==`higher`?(min.x=x,max.x):json.x==`lower`?(max.x=x,min.x):x,x/=2,
        y+=json.y==`higher`?(min.y=y,max.y):json.y==`lower`?(max.y=y,min.y):y,y/=2,
        json.turns?console.log(`{game:"${id}",turns:${json.turns},x:${x},y:${y}}`):g(x)
    ))(y=50)
))`https://crossorigin.me/http://easter_egg_hunt.andrewfaraday.com/`

\$\endgroup\$
  • \$\begingroup\$ This isn't actually code golf, you can expand a bit for clarity if you want to lol \$\endgroup\$ – Cubic Mar 30 '18 at 14:50
  • \$\begingroup\$ @Cubic, you're right, I probably should! :D I find it incredibly difficult not to crunch bytes 'round here, even when it's not code-golf! \$\endgroup\$ – Shaggy Mar 30 '18 at 15:00
1
\$\begingroup\$

Rust

As it happens, Rust has a really nice deserialization library called serde which helped a lot with this program. The algorithm itself is a rather straightforward binary search, except ran twice - once for x and once for y.

The URL macro has a specific case for no format strings because it can be done for pretty much free, and when not formatting the string would be stored in static storage.

Cargo.toml

[package]
name = "easter-egg-hunt"
version = "0.1.0"
authors = ["Konrad Borowski"]

[dependencies]
reqwest = "0.8.5"
serde = "1.0.36"
serde_derive = "1.0.36"

main.rs

extern crate reqwest;
extern crate serde;
#[macro_use]
extern crate serde_derive;

use reqwest::Client;

#[derive(Deserialize)]
struct NewGame {
    game_id: String,
}

#[derive(Deserialize)]
struct Guess {
    x: GuessStatus,
    y: GuessStatus,
    turns: Option<u32>,
}

#[derive(Deserialize)]
#[serde(rename_all = "lowercase")]
enum GuessStatus {
    Lower,
    Right,
    Higher,
}

macro_rules! url {
    ($path:expr) => {
        concat!("http://easter_egg_hunt.andrewfaraday.com", $path)
    };
    ($path:expr $(, $part:expr)*) => {
        &format!(url!($path) $(, $part)*)
    };
}

struct BinarySearch {
    low: u8,
    high: u8,
}

impl BinarySearch {
    fn new() -> Self {
        BinarySearch { low: 1, high: 100 }
    }

    fn current_guess(&self) -> u8 {
        (self.low + self.high) / 2
    }

    fn update(&mut self, guess_status: GuessStatus) {
        let current_guess = self.current_guess();
        match guess_status {
            GuessStatus::Lower => self.high = current_guess - 1,
            GuessStatus::Higher => self.low = current_guess + 1,
            GuessStatus::Right => {
                self.high = current_guess;
                self.low = current_guess;
            }
        }
    }
}

fn run_game(client: &Client) -> reqwest::Result<()> {
    let NewGame { game_id } = client.get(url!("/new_game")).send()?.json()?;
    let mut x_search = BinarySearch::new();
    let mut y_search = BinarySearch::new();
    loop {
        let x_guess = x_search.current_guess();
        let y_guess = y_search.current_guess();
        let response = client
            .get(url!("/guess/{}/{}/{}", game_id, x_guess, y_guess))
            .send()?
            .json()?;
        match response {
            Guess { x, y, turns: None } => {
                x_search.update(x);
                y_search.update(y);
            }
            Guess {
                turns: Some(turns), ..
            } => {
                println!("id:{} x:{} y:{} count:{}", game_id, x_guess, y_guess, turns);
                return Ok(());
            }
        }
    }
}

fn main() {
    let client = Client::new();
    for _ in 0..10 {
        run_game(&client).unwrap();
    }
}

Summary

id:tlxjnjtslnsnbdxyzlvn x:97 y:22 count:7
id:bbzpyhhflrdjzylwxtbr x:21 y:6 count:5
id:kcjdkfvddgxckmprxwtw x:81 y:99 count:6
id:tnzryxpkblqrqbqrqkby x:30 y:25 count:7
id:pbzkdrmjrvwmkgmlvwcb x:79 y:20 count:7
id:qxvcbcslkdmjxnffsxfb x:36 y:94 count:7
id:hqfgpdmktyfwqtbrvvly x:94 y:71 count:5
id:ytgsnssvlpnhzqzgvygw x:1 y:83 count:7
id:gjhglmkbhvswqwgrynft x:65 y:94 count:5
id:rzghpypysxtwkclgpbkx x:55 y:96 count:7

5 + 6 + 7 + 7 + 7 + 7 = 39

\$\endgroup\$
1
\$\begingroup\$

Python 2

Golfed vesrion - 276 bytes

from requests import*
u='http://easter_egg_hunt.andrewfaraday.com'
i=get('%s/new_game'%u).json()['game_id']
x=y=50;s=25;o={}
while not'turns'in o:o=get('%s/guess/%s/%d/%d'%(u,i,x,y)).json();exec("%s+=s*'rh'.rfind(o['%s'][0]);"*2)%tuple('xxyy');s=s%2+s/2
print i,x,y,o['turns']

More readable version + comments

from requests import*
u='http://easter_egg_hunt.andrewfaraday.com'
i=get('%s/new_game'%u).json()['game_id'] # get game id
x=y=50    # initial central position
s=25      # step of binary search
o={}      # initialize o to further store returns
while not'turns'in o:    # cycle until 'turns' occurs in return
    o=get('%s/guess/%s/%d/%d'%(u,i,x,y)).json()
    exec("%s+=s*'rh'.rfind(o['%s'][0]);"*2)%tuple('xxyy');s=s%2+s/2
    # this exec is used to shorten two similar lines for x and y:
    #  x += s * (... o['x'] ...); 
    #  s*'rh'.rfind(o['%s'][0]) - look at first letter of return and use its position in string 'rh' to map multipliers for step: 
    #                             -1 for lower, +1 for higher and 0 for right 
    #                             rfind() will return -1 for not finding l
    s=s%2+s/2    # divide step in two, rounding up
print i,x,y,o['turns']

Results

vjwqvbydwmbvbrhwrxqz 19 95 4
svkdvszghjzfbvqxsprt 5 12 4
dvbpnxjdgrydwffcndnt 81 67 6
qqwgsctqhdcrbywwrcxf 21 49 6
zrgqqtzjjrrsqbgvkbsm 37 12 6
trzjngljbwbwxycxpcbk 91 2 7
kysjwfzsrdjsybrchnzg 76 45 7
kcjtxqfmcgszrwkyhlkm 68 48 7
wykbjvthqmcyzscxnsxt 68 41 7
kldrfccjdphzqnqcmdgz 88 14 7

Summary

Somewhat expected :D
6+6+6+7+7+7 = 39

\$\endgroup\$
  • 2
    \$\begingroup\$ Golfers gonna golf ;) \$\endgroup\$ – AJFaraday Mar 29 '18 at 12:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.