Chapter 07

碁石を表示する

tkzwhr
tkzwhr
2023.03.21に更新

碁盤が表示できたので、次は碁盤の上に碁石を表示してみます。

今回のソースコード
src/Main.elm
module Main exposing (main)

import Browser
import Html exposing (Html, a, div, img, text)
import Html.Attributes exposing (..)


main =
    Browser.sandbox { init = init, update = update, view = view }



-- Constants


constants =
    { cellSize = 50 }



-- Types


-- ★ポイント②
type StoneColor
    = Black
    | White
    | None



-- Type Aliases


-- ★ポイント①
type alias Model =
    { boardSize : Int
    , numberOfCapturedBlackStones : Int
    , numberOfCapturedWhiteStones : Int
    , putBlackStones : List String
    , putWhiteStones : List String
    }


-- ★ポイント②
type alias Coord =
    ( Int, Int )


-- ★ポイント②
type alias Point =
    ( Coord, StoneColor )



-- Models


init : Model
init =
    { boardSize = 9
    , numberOfCapturedBlackStones = 0
    , numberOfCapturedWhiteStones = 0
    , putBlackStones = [ "A1", "B2" ]
    , putWhiteStones = [ "C1" ]
    }



-- Updates


update : msg -> Model -> Model
update _ _ =
    init



-- Views


view : Model -> Html msg
view model =
    div []
        [ goBoardView model.boardSize model.putBlackStones model.putWhiteStones
        , numberOfCapturedStonesView model.numberOfCapturedBlackStones model.numberOfCapturedWhiteStones
        ]


goBoardView : Int -> List String -> List String -> Html msg
goBoardView boardSize putBlackStones putWhiteStones =
    div
        [ style "display" "grid"
        , style "grid-template-columns" ("repeat(" ++ (boardSize |> String.fromInt) ++ ", " ++ (constants.cellSize |> String.fromInt) ++ "px)")
        , style "grid-auto-rows" ((constants.cellSize |> String.fromInt) ++ "px")
        ]
        (gen2DPointList boardSize
            |> applyStonesTo2DCoordList putBlackStones Black
            |> applyStonesTo2DCoordList putWhiteStones White
            |> List.map (cellView boardSize)
        )


cellView : Int -> Point -> Html msg
cellView boardSize ( coord, stoneColor ) =
    let
        imgView piece =
            img [ src ("assets/images/Go_" ++ piece ++ ".svg"), width constants.cellSize ] []
    in
    case stoneColor of
        Black ->
            imgView "b"

        White ->
            imgView "w"

        None ->
            a [ href ("#" ++ coordToStr coord) ] [ imgView (toGoBoardPiece boardSize coord) ]


numberOfCapturedStonesView : Int -> Int -> Html msg
numberOfCapturedStonesView black white =
    div
        [ style "display" "flex"
        , style "gap" "20px"
        , style "align-items" "center"
        , style "justify-content" "space-around"
        , style "margin-top" "20px"
        , style "padding" "10px"
        , style "background-color" "#DCB35C"
        , style "width" "250px"
        , style "font-size" "18pt"
        ]
        [ img [ src "assets/images/Go_b.svg", width constants.cellSize ] []
        , div [] [ text (black |> String.fromInt) ]
        , img [ src "assets/images/Go_w.svg", width constants.cellSize ] []
        , div [] [ text (white |> String.fromInt) ]
        ]



-- View Logics


-- ★ポイント②
gen2DPointList : Int -> List Point
gen2DPointList size =
    List.range 1 size
        |> List.map
            (\r ->
                List.range 1 size
                    |> List.map
                        (\c ->
                            ( ( r, c ), None )
                        )
            )
        |> List.reverse
        |> List.concat


toGoBoardPiece : Int -> Coord -> String
toGoBoardPiece size ( r, c ) =
    let
        rStr =
            if r == 1 then
                "d"

            else if r == size then
                "u"

            else
                "c"

        cStr =
            if c == 1 then
                "l"

            else if c == size then
                "r"

            else
                "c"
    in
    rStr ++ cStr


-- ★ポイント③
applyStonesTo2DCoordList : List String -> StoneColor -> List Point -> List Point
applyStonesTo2DCoordList putStones stoneColor original2DCoordList =
    original2DCoordList
        |> List.map
            (\( coord, originalStoneColor ) ->
                if List.any (\e -> e == coordToStr coord) putStones then
                    ( coord, stoneColor )

                else
                    ( coord, originalStoneColor )
            )


coordToStr : Coord -> String
coordToStr ( r, c ) =
    let
        -- I(64 + 9)は使われていないためスキップする
        shift =
            if r >= 9 then
                1

            else
                0

        rStr =
            Char.fromCode (64 + r + shift) |> String.fromChar

        cStr =
            String.fromInt c
    in
    rStr ++ cStr

以下、ポイントです。

碁石の配置データの持ち方

 type alias Model =
     { boardSize : Int
+    , numberOfCapturedBlackStones : Int
+    , numberOfCapturedWhiteStones : Int
+    , putBlackStones : List String
+    , putWhiteStones : List String
     }

碁石の置かれている位置を保持する必要があるため、黒石、白石の配置されている位置をそれぞれリストで持つputBlackStones, putWhiteStonesを用意しました。ついでにアゲハマ[1]の数もそれぞれ持たせています。

碁石の位置は次のように表現することにします。

  A B C D
4 ┌ ┬ ┬ ┐
3 ├ ● ┼ ┤
2 ├ ┼ ○ ┤
1 └ ┴ ┴ ●

●の石: B3, D1
○の石: C2

左下を基準にして横にアルファベット(Iは除く[2])、縦に算用数字を当てはめた方法で、欧米で一般的に使われている記法です。GNU Goでも使われており、サーバーとのやり取りで都合が良いため、この記法にしています。

前回のページで

(r, c)rn→0になるように並び替える

という操作を行っていましたが、これは、この記法では左下が原点となるためです。

他にも、左上を基準にして横に算用数字、縦に漢数字を割り当てて 2の三 と表現する記法や、左上を基準にして横と縦にアルファベットを割り当てて bc と表現する記法もあります。

また、全く別のアプローチとして、碁石が配置された位置を持つのではなく、碁石が配置された碁盤の状態を2次元配列として持つ方法もあります。

[
  ["-","-","-","-"],
  ["-","●","-","-"],
  ["-","-","○","-"],
  ["-","-","-","●"]
]

余力があればそちらの方法でも試してみると良いと思います。

碁石を碁盤に配置する

前回に取り組んだ碁盤のレイアウトでは、最終的には9パターンのいずれかになるように場合分けしていましたが、今回は黒石、白石が加わり11パターンのいずれかになるように場合分けをすることになります。

そこで、碁盤のレイアウト処理を次のように拡張します。

  1. (0, 0)(n, n)までの碁石情報付き座標リストを生成する
  2. (r, c)rn→0になるように並び替える
  3. 黒石、白石の配置リストデータを碁石情報付き座標リストに反映する
  4. 碁石の配置状態と、 座標の位置によって11パターンのSVGのいずれかを使うIMGタグを生成する

碁石情報付き2D座標リストを生成する

type StoneColor
    = Black
    | White
    | None

type alias Coord =
    ( Int, Int )

type alias Point =
    ( Coord, StoneColor )

今まではCoordという型エイリアスで座標を表現していましたが、座標への碁石の配置状態も表現するために、Pointという型エイリアスを導入しています。また、碁石の配置状態についても

  • 黒石
  • 白石
  • なし

の3パターン用意しました。

これにより、座標データの作成も、碁石が配置されていない状態で生成されるように修正しています。

gen2DPointList : Int -> List Point
gen2DPointList size =
    List.range 1 size
        |> List.map
            (\r ->
                List.range 1 size
                    |> List.map
                        (\c ->
                            ( ( r, c ), None )
                        )
            )
        |> List.reverse
        |> List.concat

黒石、白石の配置リストデータを碁石情報付き座標リストに反映する

碁石情報付き座標リストの各項目について、List#anyを用いて、配置リストデータに含まれている座標かをチェックし、含まれていれば指定された石の色に、含まれていなければもとの色を使うようにしています。

applyStonesTo2DCoordList : List String -> StoneColor -> List Point -> List Point
applyStonesTo2DCoordList putStones stoneColor original2DCoordList =
    original2DCoordList
        |> List.map
            (\( coord, originalStoneColor ) ->
                if List.any (\e -> e == coordToStr coord) putStones then
                    ( coord, stoneColor )

                else
                    ( coord, originalStoneColor )
            )

起動してみる

再度、Main.elmをコンパイルしてブラウザで表示してみましょう。

$ $ elm make src/Main.elm
Success!     

    Main ───> index.html

$ elm reactor --port 3001
Go to http://localhost:3001 to see your project dashboard.

ブラウザを起動し、 http://localhost:3001/index.html にアクセスします。

クライアントアプリの起動2

碁盤の上に、Modelの初期値で持たせている黒石と白石が配置された状態で表示されました!

次は、ゲーム用のUIを追加して遊べるようにするための準備をしていきます。

脚注
  1. 相手の石を囲んで取り上げた石のことで、お互いに打つ場所が無くなった後に相手の陣地を埋めるために使います。 ↩︎

  2. Jと紛らわしいため使われていません。 ↩︎