0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

apache、tomcat、javaでオセロゲームを作ってみた。

Posted at

私はインフラ畑を20年以上歩んできましたが
アプリケーション開発の経験はありません。

少しだけでもアプリケーション開発を経験出来ればと思い
オセロゲームの開発を考えました。

成果物はChatGPTから教わったものをそのままコピペして構築したものとなります。
内容の品質に関してはお見苦しいものもあるかと存じますが
ご容赦頂けますと幸いです。

※サーバ環境は下記記事と同一となりますので
構築手順はこちらをご参照ください。
https://qiita.com/middle-aged/items/952c837f21124ac71120

1. アプリ配置ディレクトリ作成

アプリを配置するディレクトリを下記のように作成します。

# mkdir -p /opt/tomcat/webapps/my-othello/WEB-INF
# mkdir -p /opt/tomcat/webapps/my-othello/WEB-INF/classes
# mkdir -p /opt/tomcat/webapps/my-othello/WEB-INF/lib

2. web.xmlを作成(サーブレット定義&マッピング)

/opt/tomcat/webapps/my-othello/WEB-INF/web.xmlに下記を記述します(Jakarta版/Tomcat10系想定)。

ポイント:
com.example.othello.OthelloServlet とクラス名をフル指定。
URL http://(IPアドレス):8080/my-othello/othello でアクセス可能となります。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
         https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
         version="5.0">

    <display-name>Othello Game</display-name>

    <servlet>
        <servlet-name>OthelloServlet</servlet-name>
        <servlet-class>com.example.othello.OthelloServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>OthelloServlet</servlet-name>
        <url-pattern>/othello</url-pattern>
    </servlet-mapping>

</web-app>

3. ゲームロジック OthelloGame.java 作成

とりあえず内容は、8×8 の盤・石の配置・ひっくり返し・手番管理をするクラス。

ディレクトリ作成:

mkdir -p /opt/tomcat/webapps/my-othello/WEB-INF/classes/com/example/othello

ファイル作成:

vi /opt/tomcat/webapps/my-othello/WEB-INF/classes/com/example/othello/OthelloGame.java
package com.example.othello;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

public class OthelloGame implements Serializable {

    public static final int SIZE = 8;

    public enum Stone {
        EMPTY, BLACK, WHITE;

        public Stone opposite() {
            if (this == BLACK) return WHITE;
            if (this == WHITE) return BLACK;
            return EMPTY;
        }
    }

    private Stone[][] board = new Stone[SIZE][SIZE];
    private Stone currentTurn = Stone.BLACK; // 黒からスタート

    public OthelloGame() {
        reset();
    }

    public void reset() {
        for (int r = 0; r < SIZE; r++) {
            for (int c = 0; c < SIZE; c++) {
                board[r][c] = Stone.EMPTY;
            }
        }
        // 初期配置
        board[3][3] = Stone.WHITE;
        board[3][4] = Stone.BLACK;
        board[4][3] = Stone.BLACK;
        board[4][4] = Stone.WHITE;
        currentTurn = Stone.BLACK;
    }

    public Stone getStone(int row, int col) {
        return board[row][col];
    }

    public Stone getCurrentTurn() {
        return currentTurn;
    }

    public int count(Stone stone) {
        int cnt = 0;
        for (int r = 0; r < SIZE; r++) {
            for (int c = 0; c < SIZE; c++) {
                if (board[r][c] == stone) cnt++;
            }
        }
        return cnt;
    }

    // そのマスに置けるかどうかチェック
    public boolean isValidMove(int row, int col) {
        if (!inside(row, col)) return false;
        if (board[row][col] != Stone.EMPTY) return false;
        return !getFlippableStones(row, col, currentTurn).isEmpty();
    }

    // 実際に石を置く
    public boolean play(int row, int col) {
        if (!isValidMove(row, col)) {
            return false;
        }

        List<int[]> flips = getFlippableStones(row, col, currentTurn);
        board[row][col] = currentTurn;
        for (int[] pos : flips) {
            board[pos[0]][pos[1]] = currentTurn;
        }

        // 手番交代。相手に有効手がなければパス、自分もなければ終了状態
        Stone next = currentTurn.opposite();
        if (hasAnyValidMove(next)) {
            currentTurn = next;
        } else if (hasAnyValidMove(currentTurn)) {
            // 相手が打てない場合は自分の連続手番(何もしない)
        } else {
            // 両方打てない → ゲーム終了(currentTurn は据え置き)
        }

        return true;
    }

    public boolean hasAnyValidMove(Stone turn) {
        for (int r = 0; r < SIZE; r++) {
            for (int c = 0; c < SIZE; c++) {
                if (board[r][c] == Stone.EMPTY &&
                    !getFlippableStones(r, c, turn).isEmpty()) {
                    return true;
                }
            }
        }
        return false;
    }

    public boolean isGameOver() {
        return !hasAnyValidMove(Stone.BLACK) && !hasAnyValidMove(Stone.WHITE);
    }

    private boolean inside(int r, int c) {
        return (0 <= r && r < SIZE && 0 <= c && c < SIZE);
    }

    // (row, col) に turn の石を置いたときに裏返せる座標一覧
    private List<int[]> getFlippableStones(int row, int col, Stone turn) {
        List<int[]> result = new ArrayList<>();
        int[][] dirs = {
                {-1, -1}, {-1, 0}, {-1, 1},
                { 0, -1},          { 0, 1},
                { 1, -1}, { 1, 0}, { 1, 1}
        };

        for (int[] d : dirs) {
            int dr = d[0];
            int dc = d[1];
            List<int[]> tmp = new ArrayList<>();
            int r = row + dr;
            int c = col + dc;

            // まず隣が相手の石でなければスキップ
            if (!inside(r, c) || board[r][c] != turn.opposite()) {
                continue;
            }

            // 連続する相手の石を拾う
            while (inside(r, c) && board[r][c] == turn.opposite()) {
                tmp.add(new int[]{r, c});
                r += dr;
                c += dc;
            }

            // その先に自分の石があれば有効
            if (inside(r, c) && board[r][c] == turn) {
                result.addAll(tmp);
            }
        }

        return result;
    }
}

4. サーブレット OthelloServlet.java 作成(jakarta 版)

vi /opt/tomcat/webapps/my-othello/WEB-INF/classes/com/example/othello/OthelloServlet.java
package com.example.othello;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.*;
import java.io.IOException;

@WebServlet("/othello")
public class OthelloServlet extends HttpServlet {

    private static final String SESSION_KEY = "othelloGame";

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        HttpSession session = request.getSession();
        OthelloGame game = (OthelloGame) session.getAttribute(SESSION_KEY);
        if (game == null) {
            game = new OthelloGame();
            session.setAttribute(SESSION_KEY, game);
        }

        request.setAttribute("game", game);
        request.getRequestDispatcher("/othello.jsp").forward(request, response);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        HttpSession session = request.getSession();
        OthelloGame game = (OthelloGame) session.getAttribute(SESSION_KEY);
        if (game == null) {
            game = new OthelloGame();
            session.setAttribute(SESSION_KEY, game);
        }

        String action = request.getParameter("action");
        if ("reset".equals(action)) {
            game.reset();
        } else {
            try {
                int row = Integer.parseInt(request.getParameter("row"));
                int col = Integer.parseInt(request.getParameter("col"));
                boolean ok = game.play(row, col);
                if (!ok) {
                    request.setAttribute("error", "そのマスには置けません。");
                }
            } catch (NumberFormatException e) {
                // 何もしない(不正パラメータ)
            }
        }

        request.setAttribute("game", game);
        request.getRequestDispatcher("/othello.jsp").forward(request, response);
    }
}

5. JSP 画面 othello.jsp 作成

役割:
request に入っている OthelloGame game から盤面を描画
1マスごとに

を置いて、クリックで手を打てるようにする
手番表示(黒/白)、石の数、リセットボタン表示など
vi /opt/tomcat/webapps/my-othello/othello.jsp
<%@ page contentType="text/html; charset=UTF-8" language="java" %>
<%@ page import="com.example.othello.OthelloGame" %>
<%@ page import="com.example.othello.OthelloGame.Stone" %>

<%
    OthelloGame game = (OthelloGame) request.getAttribute("game");
    if (game == null) {
        game = new OthelloGame();
    }
%>
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>オセロゲーム</title>
    <style>
        table.board {
            border-collapse: collapse;
        }
        table.board td {
            width: 40px;
            height: 40px;
            text-align: center;
            vertical-align: middle;
            border: 1px solid #333;
            background-color: #008800;
        }
        .stone-black {
            color: black;
            font-weight: bold;
        }
        .stone-white {
            color: white;
            font-weight: bold;
            text-shadow: 0 0 2px #000;
        }
        .empty-btn {
            width: 100%;
            height: 100%;
        }
    </style>
</head>
<body>
<h1>オセロゲーム(Java / Tomcat)</h1>

<p>
    手番:
    <%
        Stone turn = game.getCurrentTurn();
        if (turn == Stone.BLACK) {
    %>
        <span class="stone-black">● 黒</span>
    <%
        } else if (turn == Stone.WHITE) {
    %>
        <span class="stone-white">● 白</span>
    <%
        } else {
    %>
        -
    <%
        }
    %>
</p>

<p>
    黒:<%= game.count(Stone.BLACK) %> 
    白:<%= game.count(Stone.WHITE) %>
</p>

<%
    if (game.isGameOver()) {
        int black = game.count(Stone.BLACK);
        int white = game.count(Stone.WHITE);
        String msg;
        if (black > white)      msg = "黒の勝ち!";
        else if (white > black) msg = "白の勝ち!";
        else                    msg = "引き分け!";
%>
    <p><strong>ゲーム終了:<%= msg %></strong></p>
<%
    }
%>

<%
    String error = (String) request.getAttribute("error");
    if (error != null) {
%>
    <p style="color:red;"><%= error %></p>
<%
    }
%>

<table class="board">
    <tbody>
    <%
        for (int r = 0; r < OthelloGame.SIZE; r++) {
    %>
        <tr>
        <%
            for (int c = 0; c < OthelloGame.SIZE; c++) {
                Stone s = game.getStone(r, c);
        %>
            <td>
                <%
                    if (s == Stone.EMPTY && !game.isGameOver()) {
                %>
                    <form method="post" action="othello">
                        <input type="hidden" name="row" value="<%= r %>">
                        <input type="hidden" name="col" value="<%= c %>">
                        <button type="submit" class="empty-btn"></button>
                    </form>
                <%
                    } else if (s == Stone.BLACK) {
                %>
                    <span class="stone-black">●</span>
                <%
                    } else if (s == Stone.WHITE) {
                %>
                    <span class="stone-white">●</span>
                <%
                    }
                %>
            </td>
        <%
            }
        %>
        </tr>
    <%
        }
    %>
    </tbody>
</table>

<form method="post" action="othello" style="margin-top: 10px;">
    <input type="hidden" name="action" value="reset">
    <button type="submit">リセット</button>
</form>

</body>
</html>

6. サーブレットAPIの jar をクラスパスに指定してコンパイル

cd /opt/tomcat/webapps/my-othello/WEB-INF/classes
javac -cp .:/opt/tomcat/lib/servlet-api.jar com/example/othello/*.java

コンパイル結果を確認 ※OthelloGame.class / OthelloServlet.class があればOK
ls com/example/othello

7. Tomcat 再起動

# /opt/tomcat/bin/startup.sh

8. ブラウザからアクセス

http://<EC2のIP or ホスト名>:8080/my-othello/othello
これでオセロ盤が表示され、クリックで石が置けていれば成功です。
image.png

0
0
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
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?