This repository provides a sparring partner for a number determination game.
The intended use-case is, you write an automated game player in a programming language of your choice and have some fun.
A number is hidden from you. Your objective in the game is to determine that number, and to do so with as few steps as you can. You are provided with an oracle to do so.
The space of numbers from which the hidden number can be taken is limited in each individual game, in two ways:
- The number has precisely
D
digits. - Each individual digit is in the range
0 .. (B-1)
, whereB
is the base of the game.
Those parameters B
and D
are known before you start the game.
In each individual step, you present the oracle with one number from the guessing space. If that number matches (is the same as) the hidden number, you've completed that game successfully.
If not, you are given the oracle's verdict, in the form of two counts, the full matches count and the partial matches (or wrong position match) count.
- For the full match count, the oracle iterates through the digit positions. For each digit index, the oracle checks whether the digit in your number at that index is the same as that from the hidden number at the same index. If so, one is added to the full match count, and the pertinent digit is replaced by a unique symbol (that will never match anything) in both the hidden and your number.
- For the partial match count, the oracle searches for digit values that occure in what's left of your and the hidden number, after symbol replacement. So if your and the hidden number contain the same digit value (never mind the digit position), one is added to the partial match count and the two digits thus matched are again replaced with a unique symbol each (that will never match anything). The search continues until there is no common digit value in your and the hidden number.
You do stuff against this API by issuing a POST against an URL. You provide a JSON payload. You are supposed to set your Accept:
header to prefer application/json
, then you'll receive a JSON payload as described below.
As is customary, this API will and your software should ignore any additional JSON data not documented here.
To submit a number to game's oracle as the next step you make, you obviously need to know that game's oracle URI and the parameter's B
and D
.
An example submission for B = 6
and D = 4
might be
{ "submission": [0, 5, 1, 1] }
If the hidden code happens to be
[1, 0, 1, 3]
the oracle will answer with JSON
{ "full_match_count": 1, "partial_match_count": 2 }
You're supposed to discern yourself whether full_match_counts
equals D
, in which case you've completed the game and should discontinue use of this game's URI.
For this, you post JSON to the game-making URI like so:
{ "base": 6, "length": 4, "oracle_type": "fair" }
Of course, base
is synonymous for B
and length
synonymous for D
.
The parameter base
needs to be an integer at least 2, but no more than, say, 100, and the parameter length
an integer at least 1, but reasonably small (up to 40 or so should not pose a big problem). Try the above values for a start.
In reply, you'll receive a redirect to the oracle URI, discussed above. If you approach the oracle URI with a normal GET request, it will answer with JSON that contains that same oracle URI in a "self" field, and also the game's parameters:
{ "self": "http....", "base": 6, "length": 4, "oracle_type": "fair" }
There are three types of oracles:
- "fair" (the normal case) will dice out a random hidden number and answer your questions based on that number, as explained above.
- "nice" (a somewhat boring case) will change the hidden number in your favor upon your first request - so you always immediately receive a
full_match_count
equals tolength
. - "evil" (this may or may not get implemented any time soon) will change the hidden number from under you, so as to give you the answer it considers least informative for you. But it will be bound by whatever answers it has already given. So you cannot easily tell the difference between the evil and the fair oracle (other than through statistics or run-time observation).
Post, to the contest creation URI, JSON like so:
{ "base": 6, "length": 4, "oracle_type": "fair", "games": 100}
You can also split a contest to work with several oracles. Each oracle needs to provide a game-maing URI as above. This could be particularily interesting to make several evil oracles compete:
{
"base": 6, "length": 4, "oracle_type": "evil",
"games_per_orclefactory": 10,
"oraclefactories": [
"(game-making-URI 1)",
"(game-making-URI 2)",
...
"(game-making-URI n)"
]
}
In response to a contest creation request, you'll be redirected to a contest watch URI. A GET to that URI returns JSON like this:
{
"self": "http...(content watch URI)",
"entry": "http...(content entry URI)",
"base": 6,
"length": 4,
"oracle_type": "fair",
"games": 100
}
A team or individual software developer may enter the content by posting JSON to the content entry URI like so:
{
"team_name": "(your team name)",
"software_version": "(a version string)"
}
Both team name and software version string should be 20 characters or less.
The response will be JSON containing individual oracle URIs, each as described above.
{ "oracle_URIs": [ "http...", "http...", ... ] }
The individual URIs given will differ from team to team, but the set of hidden codes will remain the same (not necessarily in the same order, though).
The same team may enter the same competition again, with a new software version. Doing so demotes that team to "training mode". This is so people don't start playing contests twice, using the set of numbers they saw the first time for short-cuts the second time.
To be future-proof, your client should not assume that all games in a certain contest share common base
and length
values. Instead, it should fire a HTTP GET against each individual oracle URI, which will provide those values (as described above).
- Install Java 8
- Install Maven
- Have a clone of this repository on your local hard drive
- run
mvn clean install
This is a side line. If you want the real thing, just skip to the next paragraph and set up the database.
Basic oracle functionality can be accessed locally. Your main entrypoint is the static method makeRandomFairOracle(int length, int base)
in class com.innoq.numbergame.base.OracleFactory
.
There is sample code in the JUnit test base_used_by_java/src/test/java/com/innoq/numbergame/base/RandomOracleTest.java
which is run as part of mvn clean install
. To run it manually at your bash
prompt, use
r="$HOME/.m2/repository"
CLASSPATH="$r/org/scala-lang/scala-library/2.11.5/scala-library-2.11.5.jar"
CLASSPATH="$CLASSPATH:base/target/classes"
CLASSPATH="$CLASSPATH:$r/junit/junit/4.13.2/junit-4.13.2.jar"
CLASSPATH="$CLASSPATH:$r/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar"
CLASSPATH="$CLASSPATH:base_used_by_java/target/test-classes"
export CLASSPATH
java org.junit.runner.JUnitCore com.innoq.numbergame.base.RandomOracleTest
Install PostgreSQL on your machine.
Initialize a database and a database user by running the psql
command, as the postgres
admin user (or some other admin user), and type at the SQL prompt:
CREATE ROLE number_oracle_db_user LOGIN PASSWORD 'xxx';
CREATE DATABASE number_oracle_db;
\q
Next, stil as the DB admin user, run
psql -d number_oracle_db -a --variable=ON_ERROR_STOP=true -f create_db_postgres.sql
Back as the user that will be running the application, set that password as an environment value:
NUMBER_ORACLE_DB_PASSWORD=xxx
export NUMBER_ORACLE_DB_PASSWORD
(Hopefully, you changed the xxx
password to something more complicated.)
A server that aims to implement the above protocol (not yet fully functional) can be run as follows:
- Do the stuff in "How to run" above.
- Do the stuff in "Create the database" above.
- Install Play, or, more precisely, unzip
typesafe-activator-1.2.12.zip
. - Copy
activator-launch-1.2.12.jar
to the directoryoracle-rest-api
here. - Create a directory
oracle-rest-api/lib
if it's not already there, and copybase/target/base-0.3-SNAPSHOT.jar
to that directory. - In the directory
oracle-rest-api
(having theNUMBER_ORACLE_DB_PASSWORD
environment varialbe set as above), run./activator start
to fire up the server.
TODO: This need to be somewhat more automated.
Create a new nice oracle with
curl -v --data-binary '{"base": 6, "length": 4, "oracle_type": "nice"}' \
-H 'Content-Type: application/json; charset=utf-8' \
-H 'Accept: application/json' http://localhost:9000/neworacle
This should return a 303 See Other
with a Location:
header giving the URI of the newly created nice oracle.
If this is the first thing you ever did,
/oracle/1
would be that URI. Do a GET
to that
location as often as you like:
curl -v -H 'Accept: application/*' http://localhost:9000/oracle/1
This should give you 200 OK
with a JSON body containing at least:
{"base":6,"length":4,"self":"/oracle/1","type":"nice"}
You can guess (as it's a nice oracle, it'll always let you succeed on your first try):
curl -v --data-binary '{"submission": [0,2,4,5]}' \
-H 'Content-Type: application/json; charset=utf-8' \
-H 'Accept: application/json' http://localhost:9000/oracle/1
This should give you a 200 OK
and the answer
{"full_match_count":4,"partial_match_count":0}
Any oracle should be left alone after solved. If you rerun the same POST, you'd deserve, and get, a 400 Bad Request
.
The precise values you get might change.
curl -v --data-binary '{"base": 6, "length": 4, "oracle_type": "fair"}' \
-H 'Content-Type: application/json; charset=utf-8' \
-H 'Accept: application/json' http://localhost:9000/neworacle
...
HTTP/1.1 303 See Other
Location: /oracle/2
Content-Length: 0
Now
curl -v -H 'Accept: application/json' http://localhost:9000/oracle/2
gives
{"self":"/oracle/2","base":6,"length":4,"type":"fair"}
curl -v --data-binary '{"submission": [0,0,1,2]}' \
-H 'Content-Type: application/json; charset=utf-8' \
-H 'Accept: application/json' http://localhost:9000/oracle/2
In one particular example, I got
{"full_match_count":0,"partial_match_count":2}
This will of course change each time, as you
So [0,0,1,2]
gave {"full_match_count":0,"partial_match_count":2}
.
I then asked [3,4,5,0]
and got {"full_match_count":2,"partial_match_count":0}
.
I then asked [3,4,2,1]
and got {"full_match_count":2,"partial_match_count":1}
.
I next asked [2,4,2,0]
and got {"full_match_count":0,"partial_match_count":1}
.
This left [3,2,5,1]
as the only possibility, and indeed:
curl -v --data-binary '{"submission": [3,2,5,1]}' \
-H 'Content-Type: application/json; charset=utf-8' \
-H 'Accept: application/json' http://localhost:9000/oracle/2
gave (HTTP/1.1 200 OK
and)
{"full_match_count":4,"partial_match_count":0}* Closing connection #0
A further
curl -v -H 'Accept: application/json' http://localhost:9000/oracle/2
gave (HTTP/1.1 200 OK
and)
{"attempts":5,"self":"/oracle/2","solved":true,"base":6,"length":4,"type":"fair"}