Note:
Access tokens expire after 24 hours. After that, a new one is to be obtained.
| Name | Average Performance | Finished Rounds | Draw All |
|---|---|---|---|
| Total | |||
Feeling tired of playing The Retailer Game manually? If you have some knowledge in programming, you can try the competitions programmatically. You can also explore the patterns and deduce the best algorithm to solve the competitions.
Make sure you understand some basic knowledge like (url, request and response, status code, JSON and etc.) before you proceed, or you will have issues understanding the following sections.
An access token is a temporary string that the server issues to prove your identity. You need to attach your access token in every API request you send to the server.
https://retailer.casepp.com/api/robotToken
{
access_token: "TST54TkrTB_Ou0Nr4Q0O7b7iROMJYvNMxjy_ZB9hXmG",
token_type: "Bearer",
expires_in: 86400
}
Authorization: Bearer TST54TkrTB_Ou0Nr4Q0O7b7iROMJYvNMxjy_ZB9hXmG
Access tokens expire after 24 hours. After that, a new one is to be obtained.
https://retailer.casepp.com/api/freeplayRoundPriceAction
{
"seed": 12345,
"priceActions": [0, 1]
}
{
'status': 0,
'data': {
'clientRoundData': {
'weeks': [
{'price': 60, 'sales': 48,'revenue': 2880,'inventory': 1952},
{'price': 54, 'sales': 78, 'revenue': 4212, 'inventory': 1874}
],
'isCompleted': False,
'priceActions': [0, 1]
}
}
}
https://retailer.casepp.com/api/competition/robotlist
{
'status': 0,
'data':
[
{'competition_id': 'TEST_ROBOT', 'owner': 'xxxx@gmail.com'},
{'competition_id': 'ROBOT_10', 'owner': 'xxxx@gmail.com'},
{'competition_id': 'ROBOT_100', 'owner': 'xxxx@gmail.com'},
{'competition_id': 'ROBOT_1000', 'owner': 'xxxx@gmail.com'},
{'competition_id': 'ROBOT_5000', 'owner': 'xxxx@gmail.com'}
]
}
{
"competition_id": "TEST_ROBOT",
}
https://retailer.casepp.com/api/clientCompetitionData
{
'status': 0,
'student': {
'name': 'xxxx',
'email': 'xxxx@gmail.com'
},
'data': {
'competition_id': 'TEST_ROBOT', // competition_id you are requesting
'totalRounds': 6, // the total number of rounds this competition have
'isRobot': True, // this is a robot competition
'isStarted': False // a new competition with no records
}
}
{
'status': 0,
'student': {
'name': 'xxxx',
'email': 'xxxx@gmail.com'
},
'data': {
'competition_id': 'TEST_ROBOT',
'totalRounds': 6,
'isRobot': True,
'currentRound': 1, // current round you are now attempting
'clientRoundDataArr': [], // current performance data, if any
'isStarted': True
}
}
{
'status': 0,
'student': {
'name': 'xxxx',
'email': 'xxxx@gmail.com'
},
'data': {
'competition_id': 'TEST_ROBOT',
'totalRounds': 6,
'isRobot': True,
'currentRound': 6,
'clientRoundDataArr': [
{'revenue': 91497, 'optimal': 91497},
{'revenue': 65259, 'optimal': 65259},
{'revenue': 73922, 'optimal': 73922},
{'revenue': 96648, 'optimal': 108864},
{'revenue': 92269, 'optimal': 92269},
{'revenue': 79010, 'optimal': 79010}
],
'isStarted': True,
'isCompleted': True // whether the entire competition is done
}
}
optimal and revenue are only shown when you
have
completed that round, i.e., isCompleted == true.
The Competition Data API above provides an overview of your progress and performance in the competition. For the decision making process, usually you need only focus on the data of one round.
{
"competition_id": "ROBOT",
"round": 1
}
https://retailer.casepp.com/api/clientRoundData
{
'status': 0,
'student': {
'name': 'xxxx',
'email': 'xxxx@gmail.com'
},
'data': {
'competition_id': 'TEST_ROBOT',
'totalRounds': 6,
'isRobot': True,
'currentRound': 1,
'displayedRound': 1,
'clientRoundData': {
'weeks': [
{'price': 60, 'sales': 56, 'revenue': 3360, 'inventory': 1944},
{'price': 48, 'sales': 115, 'revenue': 5520, 'inventory': 1829}
],
'isCompleted': False,
'priceActions': [0, 2]
},
'isCompleted': False,
'isStarted': True,
'isView': True
}
}
As long as the competition is not completed, you can post your price decision for the current round and week.
{
"competition_id": "TEST_ROBOT",
"priceAction": 2
}
https://retailer.casepp.com/api/competitionRoundPriceAction
{
'status': 0,
'student': {
'name': 'xxxx',
'email': 'xxxx@gmail.com'
},
'data': {
'competition_id': 'TEST_ROBOT',
'totalRounds': 6,
'isRobot': True,
'currentRound': 1,
'displayedRound': 1,
'clientRoundData': {
'weeks': [
{'price': 60, 'sales': 56, 'revenue': 3360, 'inventory': 1944},
{'price': 48, 'sales': 115, 'revenue': 5520, 'inventory': 1829},
{'price': 48, 'sales': 101, 'revenue': 4848, 'inventory': 1728}
],
'isCompleted': False,
'priceActions': [0, 2, 2]
},
'isCompleted': False,
'isStarted': True,
'isView': True
}
}
You can start a new round by simply POSTing a Price Action after you finished the previous round (or at the start of a competition you never attempted before), no rocket science involved here.
At the moment, The Retailer Game limit your first week Price Action to Full Price, no matter what you actually POST.
The server will return an error message telling you the competition has already finished if you insist in POSTing more Price Actions. Of course if you are lazy counting how many rounds you have attempted, simply use this message as an ending signal.
{
'status': 1, // indicating an error
'error': 'Unauthorized access, please login or config access_token.' // access token is invalid or has expired
}
{
'status': 1, // indicating an error
'error': 'Wrong competition ID xxx', // competition_id not found
}
{
'status': 1, // indicating an error
'error': 'Competition was completed', // the competition is completed and cannot post price any more
}
{
'status': 1, // indicating an error
'error': 'Missing or invalid price', // wrong price input
}
import urllib.parse
import requests
def freePlayPriceAction(
host: str,
seed: int,
price_actions: list[int],
) -> dict:
"""Free Play mode.
Post price decisions of a round and get the round status.
Args:
host (str): the server address
seed (int): unique identifier of a round, nonnegative integer
price_actions (list[int]): list of price levels (0, 1, 2, 3) in the past
and current weeks. Prices: 0 -- $60, 1 -- $54, 2 -- $48, 3 -- $36.
Returns:
dict: round status in the form of a dict
"""
api = "/api/freeplayRoundPriceAction"
url = urllib.parse.urljoin(host, api)
payload = {"seed": seed, "priceActions": price_actions}
res = requests.post(url=url, json=payload)
return res.json()
def getCompetitionList(
host: str,
is_robot: bool = True,
) -> dict:
"""To get the list of available competitions.
Args:
host (str): the server address
is_robot (bool, optional): whether to list robot competitions or
student competitions. Defaults to True.
Returns:
dict: dict of competition list
"""
if is_robot:
# for robot only competitions
api = "/api/competition/robotlist"
else:
# can also play a standard competition using the APIs
api = "/api/competition/studentlist"
url = urllib.parse.urljoin(host, api)
res = requests.post(url=url)
return res.json()
def getCompetitionData(
host: str,
access_token: str,
competition_id: str,
) -> dict:
"""To get data about a particular competition
Args:
host (str): the server address
access_token (str): the player's access token
competition_id (str): id of the competition of interest
Returns:
dict: competition data as a dict
"""
api = "/api/clientCompetitionData"
url = urllib.parse.urljoin(host, api)
headers = {"Authorization": "Bearer " + access_token}
payload = {"competition_id": competition_id}
res = requests.post(url=url, json=payload, headers=headers)
return res.json()
def getRoundData(
host: str,
access_token: str,
competition_id: str,
round: int | None = None,
) -> dict:
"""To get data about a particular round of a competition
Args:
host (str): the server address
access_token (str): the player's access token
competition_id (str): id of the competition of interest
round (int | None, optional): round number, take the current round if
None. Defaults to None.
Returns:
dict: round data as a dict
"""
api = "/api/clientRoundData"
url = urllib.parse.urljoin(host, api)
headers = {"Authorization": "Bearer " + access_token}
payload = {"competition_id": competition_id}
if round is not None:
payload["round"] = round
res = requests.post(url=url, json=payload, headers=headers)
return res.json()
def postPriceAction(
host: str,
access_token: str,
competition_id: str,
price_action: int,
) -> dict:
"""To post a price decision to the current round of a competition
Args:
host (str): the server address
access_token (str): the player's access token
competition_id (str): id of the competition of interest
price_action (int): price decision, one of the four possible values
0, 1, 2, 3 (0 -- $60, 1 -- $54, 2 -- $48, 3 -- $36).
Returns:
dict: round data as a dict
"""
api = "/api/competitionRoundPriceAction"
url = urllib.parse.urljoin(host, api)
headers = {"Authorization": "Bearer " + access_token}
payload = {"competition_id": competition_id, "priceAction": price_action}
res = requests.post(url=url, json=payload, headers=headers)
return res.json()
if __name__ == "__main__":
### Parameters ###
HOST = "http://localhost:3001" # "https://retailer.casepp.com"
# NOTE: replace the following with your own access token!!!
ACCESS_TOKEN = "WmJjSSGiOVd_n5uVcv9LMb2jqhCQTNweta4uB-W0kYK"
### Free Play ###
# start a new round of free play of seed=12345
res = freePlayPriceAction(host=HOST, seed=12345, price_actions=[0])
print(f"response: {res}")
# move on one week with prices 1
# NOTE: price_actions is the list of all previous and current prices
res = freePlayPriceAction(host=HOST, seed=12345, price_actions=[0, 1])
print(f"response: {res}")
# move on another week with price 1
# NOTE: price_actions is the list of all previous and current prices
res = freePlayPriceAction(host=HOST, seed=12345, price_actions=[0, 1, 1])
print(f"response: {res}")
### Competition ###
# get the list of competitions for robots
competition_list = getCompetitionList(host=HOST, is_robot=True)
print(f"Competitions for robots: {competition_list}")
# get competition data (for a new competition)
competition_data = getCompetitionData(
host=HOST,
access_token=ACCESS_TOKEN,
competition_id="TEST_ROBOT",
)
print(f"Competition_data: {competition_data}")
# get round data (for a new competition)
round_data = getRoundData(
host=HOST,
access_token=ACCESS_TOKEN,
competition_id="TEST_ROBOT",
)
print(f"round_data: {round_data}")
# post the 1st price action (Week 1 price is fixed at 0, the input has no effect)
round_data = postPriceAction(
host=HOST,
access_token=ACCESS_TOKEN,
competition_id="TEST_ROBOT",
price_action=2,
)
print(f"round_data: {round_data}")
# post the 2nd price action
round_data = postPriceAction(
host=HOST,
access_token=ACCESS_TOKEN,
competition_id="TEST_ROBOT",
price_action=2,
)
print(f"round_data: {round_data}")
# get the competition data (for an ongoing competition)
competition_data = getCompetitionData(
host=HOST,
access_token=ACCESS_TOKEN,
competition_id="TEST_ROBOT",
)
print(f"Competition_data: {competition_data}")
# fix price_action at 2 and run all the way to the end
price_action = 2
while not round_data["data"]["isCompleted"]:
round_data = postPriceAction(
host=HOST,
access_token=ACCESS_TOKEN,
competition_id="TEST_ROBOT",
price_action=price_action,
)
print(round_data)
# you may set your own pricing strategy here
price_action = 2
# get the competition data (for a completed competition)
competition_data = getCompetitionData(
host=HOST,
access_token=ACCESS_TOKEN,
competition_id="TEST_ROBOT",
)
print(f"Competition_data: {competition_data}")