私はインフラ畑を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
これでオセロ盤が表示され、クリックで石が置けていれば成功です。
