0
1

More than 1 year has passed since last update.

陣取りゲーム (paizaランク A 相当)をPython3で解く

Last updated at Posted at 2021-04-18

問題の条件や定義は下記から確認できる。
変数名は基本的にpaiza解答例順序で、自分用に見やすくしたい部分などは変えたりしている。

リンクテキスト
https://paiza.jp/works/mondai/a_rank_level_up_problems/a_rank_camp_boss

必要なデータの準備

#マップ範囲、先行のプレイヤーの取得、マップ作成
H, W = map(int, input().split())
turn_player = input()
s = [list(input()) for _ in range(H)]

#A,Bそれぞれの移動先とターン数のリスト
a_point,b_point = [],[]
#AとBの初期ターン数を定義
an,bn = 1,1
#お互いに行動可能かどうかのフラグ
turn_change_frag = True

#A,Bのスタート地点特定
for y in range(H):
    for x in range(W):
        if s[y][x] == "A":
            a_point.append([y,x,1])
        elif s[y][x] == "B":
            b_point.append([y,x,1])

A,Bのスタート地点特定については総当りじゃなくもっとピンポイントにできないのかと思いはしたのだけど、paiza解答も同じ事をやっていたので「そういうものかなあ…」という気持ちでいる。

ターンプレイヤーのみがループ文の中で行動できるようにする

#先行決定
if turn_player == "A":
    turn_point,false_point = a_point,b_point
    rpaint,false_rpaint = "A","B"
    nou_n,false_n = an,bn

elif turn_player == "B":
    turn_point,false_point = b_point,a_point
    rpaint,false_rpaint = "B","A"
    nou_n,false_n  = bn,an

A,Bそれぞれのデータをターンプレイヤーが切り替わるごとに入れ替わるようにしようと考えた。
ターンプレイヤーで無い側(false側)の変数は切り替え時以外書く事がないしスマートな気がした。

turn_player変数をこれ以降使ってない。もっと変数宣言を節約できそうな気がしてる。

ループ部分

#陣地がA,Bで埋まるまでループ
while len(turn_point) > 0:

    #ターンプレイヤーが直近で確保した陣地、ターン数
    [y, x, n] = turn_point[0]

    #連続でターンを行おうとしている場合、ターンを交代する
    if n > nou_n and turn_change_frag:
        nou_n += 1
        nou_n,false_n = false_n,nou_n
        turn_point,false_point = false_point,turn_point
        rpaint,false_rpaint = false_rpaint,rpaint
        continue

    #次の行動に映るので直近で確保した陣地のデータをリストから消す
    turn_point.pop(0)

    #ターンプレイヤーが4方向の陣地を可能な限り確保する
    if y > 0 and s[y - 1][x] == ".":
        s[y - 1][x] = rpaint
        turn_point.append([y - 1, x, n + 1])
    if y < H - 1 and s[y + 1][x] == ".":
        s[y + 1][x] = rpaint
        turn_point.append([y + 1, x, n + 1])
    if x > 0 and s[y][x - 1] == ".":
        s[y][x - 1] = rpaint
        turn_point.append([y, x - 1, n + 1])
    if x < W - 1 and s[y][x + 1] == ".":
        s[y][x + 1] = rpaint
        turn_point.append([y, x + 1, n + 1])

    #ターンプレイヤーの行き場所がなくなったら相手に永続的にターンを渡す
    if not turn_point:
        turn_change_frag = False
        turn_point,false_point = false_point,turn_point
        rpaint,false_rpaint = false_rpaint,rpaint

ループするための条件を考えるのが迷った。
どちらかが行動できる間は当然ターンプレイヤーの行動リストに何か要素が入っているはずなのでwhileの条件は単純に。
このままだとA,Bの行動回数が違う場合でも同じターン数に行動が止まってしまうのでループ文の最後に条件を書いてみた。

ターンプレイヤーが変わった時に変数を反転させて管理をするのは結構お気に入りかもしれない(現在の実力からすると)

結果発表

#AとBの陣地の範囲を数えましょう。
a_area,b_area = 0,0

for y in range(int(H)):
    for x in range(int(W)):
        if s[y][x] == "A":
            a_area +=1
        elif s[y][x] == "B":
            b_area +=1

#勝利判定            
print(a_area,b_area)

if a_area > b_area:
    print("A")
elif b_area > a_area:
    print("B")

noobだとしてもウィニングランな部分。

ループ文の中でAとBをカウントしてないので今からリストから拾う。書いてる間は極力ループ文の中がごっちゃりしてほしくなかったという理由が大きい。

コード全体

H, W = map(int, input().split())
turn_player = input()
s = [list(input()) for _ in range(H)]
a_point,b_point = [],[]
an,bn = 1,1
turn_change_frag = True

#A,Bのスタート地点特定
for y in range(H):
    for x in range(W):
        if s[y][x] == "A":
            a_point.append([y,x,1])

        elif s[y][x] == "B":
            b_point.append([y,x,1])

#先行決定
if turn_player == "A":
    turn_point,false_point = a_point,b_point
    rpaint,false_rpaint = "A","B"
    nou_n,false_n = an,bn

elif turn_player == "B":
    turn_point,false_point = b_point,a_point
    rpaint,false_rpaint = "B","A"
    nou_n,false_n  = bn,an

#陣地がA,Bで埋まるまでループ
while len(turn_point) > 0:
    [y, x, n] = turn_point[0]

    #ターン交代
    if n > nou_n and turn_change_frag:
        nou_n += 1
        nou_n,false_n = false_n,nou_n
        turn_point,false_point = false_point,turn_point
        rpaint,false_rpaint = false_rpaint,rpaint
        continue

    turn_point.pop(0)

    #ターンプレイヤーが4方向の陣地を確保する
    if y > 0 and s[y - 1][x] == ".":
        s[y - 1][x] = rpaint
        turn_point.append([y - 1, x, n + 1])
    if y < H - 1 and s[y + 1][x] == ".":
        s[y + 1][x] = rpaint
        turn_point.append([y + 1, x, n + 1])
    if x > 0 and s[y][x - 1] == ".":
        s[y][x - 1] = rpaint
        turn_point.append([y, x - 1, n + 1])
    if x < W - 1 and s[y][x + 1] == ".":
        s[y][x + 1] = rpaint
        turn_point.append([y, x + 1, n + 1])

    #ターンプレイヤーの行き場所がなくなったら相手に永続的にターンを渡す
    if not turn_point:
        turn_change_frag = False
        turn_point,false_point = false_point,turn_point
        rpaint,false_rpaint = false_rpaint,rpaint


#勝利判定
a_area,b_area = 0,0

for y in range(int(H)):
    for x in range(int(W)):
        if s[y][x] == "A":
            a_area +=1
        elif s[y][x] == "B":
            b_area +=1

print(a_area,b_area)

if a_area > b_area:
    print("A")
elif b_area > a_area:
    print("B")

感想とか

ターンプレイヤーを正しいタイミングで切り替えるのに一番手間取りました。
考え方の流れとして

1:ターンプレイヤーが連続で行動しようとしないかチェックすればいい
2:お互いのプレイヤーがそれぞれ何ターン行動したかを監視する
3:前のターンと比較して本当に連続で行動しようとしたのか判定する(ここがわかりにくかった)
4:連続で「行動しようとしたら」ターンプレイヤーを渡す(continueする)

という感じでふんわりとした言葉からどうコードに落とし込むか考えていきました。

正解後にpaiza解答例を読んだけど、条件文の書き方とかを見て引き出しの量が全然違うなと実感。それは当然ではあるんだけど。

演算子をこねくり回して書ける範囲のものなら直感ですぐ書けるようになりたい。

おまけ:コードの実行時間を比較してみる

自分のコード
dc471e674d1fc03e793b1b91a155941e.png

paiza解答例
7ef2010374cc8022b29d1c3018f953af.png

自作コードがだいたい平均1.5秒でpaiza解答例が1.8秒だった、これは嬉しいかも。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1