システムの詳しい内容は System.md を参照
httpx, pandas, requests
pip install httpx pandas requests
pip3 install httpx pandas requests
solverファイルは必ずsimulatorをインポートすること(行頭にimport simulator
)
ファイルの終端にsimulator.setSimulator("solver_name", solverFunc)
が必要
solveList.pyの終端にimport solverFileName
を付け加える
削除・変更したい場合は追加する際と同様にすると元に戻せる
preview.pyを実行し、read関数かrealRead関数を実行すると過去の試合データが確認できます
詳しい操作方法はこのファイルの下の方に書いてありますが、help関数からも確認できます
solver関数はシミュレート時呼び出され、試合の情報を取得したり職人の行動を決定することが出来ます 必ず引数は以下の形式に従ってください
def solverFunc(interface, solver)
自作クラスについて以下に示します
solver関数の第1引数のinterfaceにはInterfaceクラスが渡されます 内容は以下の通りです
Interface.getMatchInfo(): MatchInfoクラス
試合の情報がMatchInfoクラスで返される(後述) 通信に失敗した場合はNoneが返り値
Interface.postMovement(data): bool
職人の行動を決定する(サーバーにPOSTする) 通信に失敗した場合はFalseが返り値
dataの形式は2通り利用できる
[[0, 0], [1, 8], [2, 8]]
[{"type": 0, "dir": 0}, {"type": 1, "dir": 8}, {"type": 2, "dir": 8}]
基本的に次の自分のターンに対しての行動を決定する
Interface.setTurn(turn): None
Interface.postMovementの直前に使用することで次のターン以外にも行動を設定することが出来る
Interface.turn: int
Interfaceが次にPOSTするターン
無効なターン数にもなり得る(ただし、POSTの際に中断する)
MatchInfoクラス、及び内部に生成されるBoardクラスの内容は以下の通りです
MatchInfo.turn: int
現在のターン数
MatchInfo.turns: int
この試合の総ターン数
MatchInfo.myTurn: bool
次のターンが自分のターンか否か (先手チームならturn = [0, 2, 4, 6...]の時にTrue)
MatchInfo.first: bool
この試合において自分が先手か否か
MatchInfo.board: Boardクラス
現在のフィールドの情報をBoardクラスで表したもの
MatchInfo.logs: list
過去の職人の行動 詳しくは公式の試合状態取得API Responsesのlogsへ
例
{
"turn": 1,
"actions": [
{
"succeeded": true,
"type": 0,
"dir": 0
}
]
}
MatchInfo.myLogs: list
MatchInfo.logsから自チームのターンのみ取り出したもの
MatchInfo.otherLogs: list
MatchInfo.logsから相手チームのターンのみ取り出したもの
MatchInfo.turnTime: int
1ターンの時間
MatchInfo.other: str
相手チームを表す文字列(名前的なもの)
Board.height, Board.width: int
フィールドの大きさ フィールドを2次元配列で表す際は長さheightの配列の中に長さwidthの配列が入る形になる
Board.mason: int
職人の人数 自他共に同じ人数の職人がいる
Board.walls: Matrix<list<list<int>>>
城壁の存在を表す2次元配列 {0: なし, 1: 自チームの城壁, 2: 他チームの城壁}
Board.territories: Matrix<list<list<int>>>
陣地の情報を表す2次元配列 {0: なし, 1: 自チーム, 2: 他チーム, 3: 両チーム}
Board.structures: Matrix<list<list<int>>>
構造物の情報を表す2次元配列 {0: 平地, 1: 池, 2: 城}
Board.masons: Matrix<list<list<int>>>
職人の存在を表す2次元配列
自然数は自チームの職人、負の数は他チームの職人 絶対値が職人のidを表す 0の時不在
Board.all: Matrix<list<list<Fieldクラス>>>
2次元配列で表される情報をまとめたもの Fieldクラスは以下の通り
Field.wall: int Field.territory: int Field.structure: int Field.mason: int
いずれも2次元配列から取り出す場合の値と同じ
Board.myMasons: list
自チームの職人の位置を返す idが1始まりなのに対しインデックスは0始まりなので注意
例: Board.myMasons[0] が [3, 6] のとき Board.masons[3][6] は 1
Board.otherMasons: list
他チームの職人の位置を返す
Board.castles: list
フィールド上の城の位置を返す
solver関数の第2引数のsolverにはSolverクラスが渡されます 内容は以下の通りです
solver.isAlive(): bool
試合が続行可能かどうかを返す
Falseが返された場合即座にsolver関数を終了してください(終了処理を円滑に進めるため)
solver.flag: dict<int, tuple<int>>
GUIで入力された座標を職人のIdと結びつけてdictで返す
GUIによってデータが指定されていない場合、Noneが代入されている
Falseが返された場合即座にsolver関数を終了してください(終了処理を円滑に進めるため)
solverファイルに必ずインポートする必要があるsimulatorモジュールには便利な変数及び関数を用意しておきました 以下に示します
simulator.setSimulator(name, solver): None
solverをsolver関数として登録する
control.pyではnameを用いて管理するため注意すること 拡張子は不要 ファイル名に指定できない文字は指定不可
directionList: ((-1, 0), (0, 1), (1, 0), (0, -1), (1, -1), (-1, -1), (1, 1), (-1, 1))
directionSet: ((2, -1, 0), (4, 0, 1), (6, 1, 0), (8, 0, -1), (1, -1, -1), (3, -1, 1), (5, 1, 1), (7, 1, -1))
fourDirectionList: ((-1, 0), (0, 1), (1, 0), (0, -1))
fourDirectionSet: ((2, -1, 0), (4, 0, 1), (6, 1, 0), (8, 0, -1))
eightDirectionList: ((-1, -1), (-1, 0), (-1, 1), (0, 1), (1, 1), (1, 0), (1, -1), (0, -1))
eightDirectionSet: ((1, -1, -1), (2, -1, 0), (3, -1, 1), (4, 0, 1), (5, 1, 1), (6, 1, 0), (7, 1, -1), (8, 0, -1))
全方位にアクセスする際のインデックスの差分及びそのidをタプルにしたもの
for文で回した際、directionList,Setは上下左右が先に、eightDirectionList,Setはインデックス通りになっています
simulator.Matrix: listを継承したクラス
2次元配列に対して容易にアクセスできる
Matrix[(x, y)]のような配列での指定が可能に
また、Boardクラスには様々なメソッドが用意されています
Board.inField(x, y): bool
与えられた座標がBoardクラスの中に存在するか否かを返す
次のように使ってください
for dir, dx, dy in simulator.directionSet:
newX, newY = x+dx, y+dy
if not Board.inField(newX, newY)
引数を2つにして座標の配列で渡すこともできます
Board.inField([500000, -123]) -> False
Board.allDirection(x, y, directions): iter<list<int>>
directionsにdirectionList及びその種類のものを入れることで全方位に対する数値を出してくれる
また、boardの範囲外のものは返さない
次のように使えます
for dir, x, y in Board.allDirection(oldX, oldY, directionSet):
for x, y in Board.allDirection(oldX, oldY, directionList):
なおイテレータ型なので繰り返し使う際はlistに変換してください
引数を2つにして座標の配列で渡すこともできます
Board.allDirection([-1, -1], directionSet) -> iter([(5, 0, 0)])
Board.distance(x, y, *, destroy=False, other=False): Matrix<tuple<tuple<int>>>
与えられた座標への距離を幅探索し、2次元配列で返します 到達できない箇所は-1が返されます また、存在しない座標が与えられるとNoneを返します
destroyをTrueにすることで城壁を破壊する行動を考慮に入れます(が、destroyをTrueにすると必ずposからの移動となるので必要な場合はreverseDistanceを使ってください)
otherをTrueにすると相手チームの立場からみた結果を返します
引数を1つにして座標の配列で渡すこともできます
Board.distance([500000, -123]) -> None
O(height*width), メモ化を行っているため同じboard、地点で2回目以降O(1)
Board.reverseDistance(x, y, *, other=False): Matrix<tuple<tuple<int>>>
ほぼdistanceと同じですが、distanceとは逆に、指定された地点にどのくらいのターン数で到達可能かを返します
これはrouteメソッドなどの実装に大いに役に立ちます
常にdestroy=Trueとなるものを返します
otherをTrueにすると相手チームの立場からみた結果を返します
O(height*width), メモ化を行っているため同じboard、地点で2回目以降O(1)
Board.nearest(pos, targets, *, destroy=False, other=False): targets[...]
与えられたBoardクラス、または距離を表す2次元配列から、targetsのうち最も近いものを返します また、存在しない座標が与えられるとNoneを返します
destroyをTrueにすることで城壁を破壊する行動を考慮に入れます
otherをTrueにすると相手チームの立場からみた結果を返します
次のように使えます
castle = Board.nearest(mason, castles)
座標は必ず配列にする必要はなく、targetsも複数の引数として渡して構いません(targets内で複数の形式を混合するのはやめてください)
Board.nearest(500000, -123, castle1, castle2, castle3) -> None
O(|targets|) distanceを内部で呼び出すため、distanceが計算されていない地点ではO(height*width)
Board.outline(targets, directions): list<list<int>>
与えられたtargetsの外に接する輪郭を返します
城を囲むときに必要な城壁の位置を確認したい時などに便利です
directionsによって輪郭の方角を変えることが出来ます
次のように使えます
walls = Board.outline(castles, simulator.fourDirectionList)
O(|targets|)
Board.around(targets, directions): list<list<int>>
与えられたtargetsに対してdirectionsだけ座標をずらしたときの全ての位置を返します
城壁を建てるために移動すべき位置を確認したい時などに便利です
次のように使えます
targets = Board.around(walls, simulator.fourDirectionList)
O(|targets|)
Board.frame(targets, directions): targets[...]
与えられたtargetsの内に接する輪郭を返します
厳密にはdirectionsの方向にtargetsが含まれない箇所を返します
O(|targets|)
Board.area(outline, directions): list<list<list<int>>>
与えられたoutlineによってフィールド全体を分割した結果を返します
池で区切られたエリアを確認したい時などに便利です
directionsの方向に接続できるかどうかを判定します
Board.reachAble(pos, targets, directions=directionSet, mason=False, *, other=False): targets[...]
posから移動可能なtargetsを返します
masonをTrueにすると職人が存在する箇所を移動不可とします
otherをTrueにすると相手チームの立場からみた結果を返します
Board.route(pos, target, directions=directionSet, destroy=True, *, other=False): list<list<int>>
posからtargetに移動する際のルートをポストするmovementと同じ形式で返します
destroy=Falseとすると壁破壊を行わなくなります
other=Trueとすると相手チームの立場から見た結果を返します
O(Board.distance(...)) ルートの長さによって変動します
reverseDistanceを内部で呼び出すため、計算されていない地点ではO(height*width)
Board.firstMovement(pos, target, directions=directionSet, destroy=True, *, other=False): list<list<int>>
posからtargetに移動する際、次に行うべき行動を返します
destroy=Falseとすると壁破壊を行わなくなります
other=Trueとすると相手チームの立場から見た結果を返します
O(1)
reverseDistanceを内部で呼び出すため、計算されていない地点ではO(height*width)
Board.calcPoint(): list<list<int>>
与えられたBoardクラスでの現在の自チームの点数、相手チームの点数を返します
[
[自チームの合計点数, 自チームの城のみの点数, 自チームの陣地のみの点数],
[相手チームの合計点数, 相手チームの城のみの点数, 相手チームの陣地のみの点数]
]
また、いくつかのBoardクラスのメソッドはsimulatorの関数から呼び出せます
simulator.inField(board, x, y): bool
simulator.allDirection(board, x, y, directions): iter<list<int>>
simulator.distance(board, x, y): tuple<tuple<int>>
simulator.nearest(board, pos, targets): targets[...]
simulator.calcPoint(board): list<list<int>>
previewファイルでは過去に保存された試合データの表示、実行時データの取り出しが行えます 以下に利用可能な関数を示します
preview.read(log=None): None
与えられたデータをGUIで表示します
solverを2つ、フィールドのタイプ、ターン数と実行時間、先手後手を選択するとその練習時に保存されたログデータが呼び出されて試合が復元され、GUIが表示されます
入力したデータが間違っているか、指定された試合データが存在しなければエラーとなります
また、logには指定の形式の文字列(/matches/{id}のGETレスポンスのうちのlogsを取り出したjson文字列など)を渡すことができ、ファイル上に保存されていない試合についても表示することが可能です(詳しくはSYSTEM.md参照)
preview.realRead(matchId=None): None
保存されているデータをGUIで表示します
指定された試合Idのデータを復元し、read関数を内部で呼び出してGUIを表示します
preview.setTurn(turn): None
現在表示している試合について指定のターン数の盤面を復元し表示します
また、preview.pyからGUIを実行している際は下に表示されるバーから指定のターンへ飛ぶことができます
preview.getMatchInfo(turn=None): simulator.MatchInfo
現在表示している試合、ターン数におけるinterface.Interface.getMatchInfo()の返り値と同じものを返します
turnにターン数を指定することでそのターン数の結果を返します
preview.increment(turn=None): None
現在表示している試合のターン数をキー入力で1ターンずつ切り替えることができる関数です
1ターンずつ戻すことも可能です
turnにターン数を指定するとそのターン数から開始します
preview.auto(turn=None): None
現在表示している試合のターン数を0.5秒ごとに1ターン、最終ターンまで切り替え続けます
turnにターン数を指定するとそのターン数から開始します
試合結果はcontrol.pyで確認できますがresultフォルダからも確認できます
solver関数が異常終了した場合は点数を-1として記録します
なお、試合中に正常終了しなかった場合(Ctrl-Cで中断された場合等)は記録されません