<Java>オセロの作り方 - 初めてゼロから作ったプログラム -
Javaの勉強のためにオセロを作りました。
初めてゼロから作ったプログラムです。
勉強のためなので無駄がたくさんあります。
そもそも作者のプログラミングレベルが低いのであしからず。
作り方
駒を反転する際のロジックにはちょうど良い記事がありましたので流用しています。
https://techacademy.jp/magazine/22228
言語:Java
技術:JSP/Servlet , JSTL , EL式 , SetterGetter , セッションスコープ , フィルタ
ファイル構成
Model:Reversi.java , Logic.java
View:index.jsp
Controller:Servlet.java
Filter:Filter.java
ファイル構成はMVCモデルに沿ってみました。
1. まずは 画面を作成します。
ポイント
- JSPでデータを作成
- フィルタを使用
- テーブルをforEachで回してる
- 現在の駒数をカウントする
- 待ったボタン、パスボタン、最初からボタンを作成
- ボタンを丸くしているところ
- 画像などは一切使わずにtableタグやテキストだけでデザインしたところ
index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ page import="models.Reversi" %> <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>オセロ</title> <link rel="stylesheet" href="<c:url value='/css/style.css' />"> </head> <body> <header> <h1>オセロ</h1> <p>はじめて開発したゲーム</p> <h2>${ reversi.msg }</h2> </header> <main> <form action="/othello/Servlet" method="get"> <table> <tr> <th></th> <th>1</th> <th>2</th> <th>3</th> <th>4</th> <th>5</th> <th>6</th> <th>7</th> <th>8</th> <th></th> </tr> <% int i = 0; int j = 0; %> <c:forEach var="sarr" items="${ reversi.board }"> <tr> <th><%= i+1 %></th> <c:forEach var="s" items="${ sarr }"> <% String set = i+"_"+j; %> <td><input type="submit" value="${ s }" name="<%= set %>" class="set"></td> <% j++; %> </c:forEach> </tr> <% i++; j = 0; %> </c:forEach> </table> <p>BLACK : ${ reversi.cnt_black } WHITE : ${ reversi.cnt_white }</p> <div> <input type="submit" value="待った" name="stop" class="stop"/> </div> <div> <input type="submit" value="パス" name="pass" class="pass"/> </div> <div class=""> <button type="submit" value="最初から" name="reset" class="reset">最初<br>から</button> </div> </form> </main> <footer> <p>by Amanjack</p> </footer> </body> </html>
2. CSSの作成
style.css
@charset "UTF-8"; body{ text-align:center; font-size:7pt; display: flex; flex-flow: column; min-height: 100vh; } main { flex:1; } footer{ margin-bottom:10px; } table{ border-collapse:collapse; margin-left:auto; margin-right:auto; table-layout:fixed; } table th, table td{ width:25px; height:25px; } table th{ font-size:70%; } table td{ border:solid 1px black; } .set{ width:25px; height:25px; border-style:none; background-color:#ffffff; font-size:12pt; } .set:hover{ background-color:#cccccc; } button, input[type="submit"]{ background-color: transparent; border: none; cursor: pointer; outline: none; padding: 0; appearance: none; } div{ display: inline-block; width: 40px; height: 40px; border-radius: 50%; border:solid 1px black; margin:15px; } .stop, .pass, .reset{ font-size:8pt; width: 38px; height: 38px; border-radius: 50%; text-align: center; vertical-align:middle; margin:1px; background-color:white; } .stop{ padding:0 0 2px 0; } .reset{ line-height:12px; } .stop:hover, .pass:hover, .reset:hover{ background-color:#cccccc; }
3. JavaBeans的なファイルの作成
ポイント
- 作る必要はなかったが勉強のために作成
- addメソッド、removeメソッドclearメソッドなどカスタマイズ
Reversi.java
package models; import java.util.ArrayList; public class Reversi { // ゲーム進行用フラグ private boolean game; private boolean fin; private boolean ok_reverse; // 8×8の盤面のデータを格納するインスタンス private String[][] board; // 何手目かカウントする変数 private int cnt_turn; private ArrayList<String[][]> record; // 配列に格納するオセロのデータ static final String EMPTY = " "; static final String BLACK = "●"; static final String WHITE = "○"; // 置く石と反転される石 private String stone; private String rev_stone; // ゲーム進行用メッセージ private String msg; // 石の駒数のカウント private int cnt_black; private int cnt_white; public Reversi() { } public Reversi(boolean game, boolean fin, boolean ok_reverse, String[][] board, int cnt_turn, ArrayList<String[][]> record, String stone, String rev_stone, String msg, int cnt_black, int cnt_white) { super(); this.game = game; this.fin = fin; this.ok_reverse = ok_reverse; this.board = board; this.cnt_turn = cnt_turn; this.record = record; this.stone = stone; this.rev_stone = rev_stone; this.msg = msg; this.cnt_black = cnt_black; this.cnt_white = cnt_white; } public boolean isGame() { return game; } public void setGame(boolean game) { this.game = game; } public boolean isFin() { return fin; } public void setFin(boolean fin) { this.fin = fin; } public boolean isOk_reverse() { return ok_reverse; } public void setOk_reverse(boolean ok_reverse) { this.ok_reverse = ok_reverse; } public String[][] getBoard() { return board; } public String[] getBoard(int i) { return board[i]; } public String getBoard(int i, int j) { return board[i][j]; } public void setBoard(String[][] board) { this.board = board; } public void setBoard(int i, int j, String value) { this.board[i][j] = value; } public int getCnt_turn() { return cnt_turn; } public void setCnt_turn(int cnt_turn) { this.cnt_turn = cnt_turn; } public ArrayList<String[][]> getRecord() { return record; } public String[][] getRecord(int i) { return record.get(i); } public void setRecord(ArrayList<String[][]> record) { this.record = record; } public void addRecord(String[][] new_board) { this.record.add(new_board); } public void removeRecord(int i) { this.record.remove(i); } public void clearRecord() { this.record.clear(); } public String getStone() { return stone; } public void setStone(String stone) { this.stone = stone; } public String getRev_stone() { return rev_stone; } public void setRev_stone(String rev_stone) { this.rev_stone = rev_stone; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public int getCnt_black() { return cnt_black; } public void setCnt_black(int cnt_black) { this.cnt_black = cnt_black; } public int getCnt_white() { return cnt_white; } public void setCnt_white(int cnt_white) { this.cnt_white = cnt_white; } }
4. submitボタンの受け皿となるServletの作成
ポイント
- ぬるぽが出ないように作成
- セッションスコープの利用
package controllers; import java.io.IOException; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import models.Logic; import models.Reversi; /** * Servlet implementation class Servlet */ @WebServlet("/Servlet") public class Servlet extends HttpServlet { private static final long serialVersionUID = 1L; /** * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) */ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); HttpSession session = request.getSession(); Reversi r = (Reversi) session.getAttribute("reversi"); if(r != null){ // ゲームが終了した場合 if(r.isFin()){ Logic.initialize(r); session.setAttribute("reversi", r); } else { // 盤面からリクエストを受けた場合 for (int i = 0; i < 8; i++) { for (int j = 0; j < 8; j++) { String param = i+"_"+j; if (!(request.getParameter(param) == null || request.getParameter(param).isEmpty())) { Logic.setStone(r, j, i); session.setAttribute("reversi", r); } } } // "待った"ボタンが押された場合 if (!(request.getParameter("stop") == null || request.getParameter("stop").isEmpty())) { Logic.stop(r); session.setAttribute("reversi", r); // "パス"ボタンが押された場合 } else if (!(request.getParameter("pass") == null || request.getParameter("pass").isEmpty())) { Logic.pass(r); session.setAttribute("reversi", r); // "最初から"ボタンが押された場合 } else if (!(request.getParameter("reset") == null || request.getParameter("reset").isEmpty())) { Logic.initialize(r); session.setAttribute("reversi", r); } } } else { // インスタンスがnullの場合 Reversi reversi = new Reversi(); Logic.initialize(reversi); session.setAttribute("reversi", reversi); } RequestDispatcher rd = request.getRequestDispatcher("/index.jsp"); rd.forward(request, response); } }
5. ロジック専用のJavaファイルの作成
ポイント
- 初期化するメソッド initialize()
- 駒をセットするメソッド setTurn()
- 駒を反転するメソッド turnStone()
- パスするメソッド pass()
- 駒をカウントするメソッド cnt()
- 盤面を戻すメソッド stop()
駒を反転するロジックは下記サイトから流用
https://techacademy.jp/magazine/22228
Logic.java
package models; import java.util.ArrayList; public class Logic { // 初期設定するメソッド static public void initialize(Reversi reversi) { // インスタンスの生成 String[][] board = new String[8][8]; ArrayList<String[][]> record = new ArrayList<String[][]>(); // 初期化 reversi.setGame(false); reversi.setFin(false); reversi.setOk_reverse(false); reversi.setBoard(board); reversi.setRecord(record); reversi.setCnt_turn(0); reversi.setCnt_black(0); reversi.setCnt_white(0); //オセロ版の要素を全てクリアする for (int i = 0; i < 8; i++) { for (int j = 0; j < 8; j++) { board[i][j] = Reversi.EMPTY; } } //初期状態の配置 board[3][3] = Reversi.BLACK; board[3][4] = Reversi.WHITE; board[4][3] = Reversi.WHITE; board[4][4] = Reversi.BLACK; // 0手目の盤面をコピーする add_record(reversi); reversi.setCnt_black(2); reversi.setCnt_white(2); //次うつ駒の色を指定 reversi.setStone(Reversi.BLACK); reversi.setRev_stone(Reversi.WHITE); reversi.setMsg(reversi.getStone() +"のターンです"); //ゲーム実行中フラグ reversi.setGame(true); } static public void add_record(Reversi reversi) { String[][] new_board = new String[8][8]; for (int i = 0; i < 8; i++) { for (int j = 0; j < 8; j++) { new_board[i][j] = reversi.getBoard(i,j); } } reversi.addRecord(new_board); } static public void setStone(Reversi reversi, int x, int y) { // 位置名称の変数のセット int a = x+1; int b = y+1; String position = a+" - "+b; // 駒を配置できる場合 if ((reversi.getBoard(y,x)).equals(Reversi.EMPTY)) { // 駒をセット reversi.setBoard(y, x, reversi.getStone()); // ひっくり返す処理 turnStone(x, y, reversi); // ひっくり返す場所があった場合と // なかった場合の処理 if(reversi.isOk_reverse()) { // 駒の集計 cnt(reversi); // 次うつ駒の設定 String next_rev_storn = reversi.getStone(); reversi.setStone(reversi.getRev_stone()); reversi.setRev_stone(next_rev_storn); // 現在の盤面を手数ごとに格納 reversi.setCnt_turn(reversi.getCnt_turn()+1); add_record(reversi); } else { reversi.setMsg(position+"に駒は置けません"); reversi.setBoard(y, x , Reversi.EMPTY); } } else { // 既に駒がおいてある位置を指定した場合 reversi.setMsg(position+"にはすでに駒が置いてあります"); } } static public void cnt(Reversi reversi) { //駒の集計をする処理 boolean existempty = false; reversi.setCnt_black(0); reversi.setCnt_white(0); for (String[] sarr : reversi.getBoard()) { for (String s : sarr) { //空いている座標があるか、各駒数の集計 if (s.equals(Reversi.EMPTY)) { existempty = true; } else if (s.equals(Reversi.BLACK)) { reversi.setCnt_black(reversi.getCnt_black()+1); } else if (s.equals(Reversi.WHITE)) { reversi.setCnt_white(reversi.getCnt_white()+1); } } } if (existempty) { reversi.setMsg(reversi.getRev_stone() + "のターンです"); } else if(reversi.getCnt_black() > reversi.getCnt_white()) { reversi.setMsg(Reversi.BLACK + "の勝ちです"); reversi.setFin(true); } else if(reversi.getCnt_white() > reversi.getCnt_black()) { reversi.setMsg(Reversi.WHITE + "の勝ちです"); reversi.setFin(true); } else if(reversi.getCnt_white() == reversi.getCnt_black()) { reversi.setMsg("引き分けです"); reversi.setFin(true); } } static public void stop(Reversi reversi) { if(reversi.getCnt_turn() > 0) { // 前回の盤面に戻す処理 reversi.setCnt_turn(reversi.getCnt_turn()-1); reversi.setBoard(reversi.getRecord(reversi.getCnt_turn())); // 不要な盤面の削除 reversi.removeRecord(reversi.getCnt_turn()+1); // 次うつ駒の設定 String next_rev_storn = reversi.getStone(); reversi.setStone(reversi.getRev_stone()); reversi.setRev_stone(next_rev_storn); reversi.setMsg(reversi.getStone()+"のターンです"); } else if(reversi.getCnt_turn() == 0) { reversi.setMsg("一手目です"); } } static public void pass(Reversi reversi) { // 次うつ駒の設定 String next_rev_storn = reversi.getStone(); reversi.setStone(reversi.getRev_stone()); reversi.setRev_stone(next_rev_storn); // 現在の盤面を手数ごとに格納 reversi.setCnt_turn(reversi.getCnt_turn()+1); add_record(reversi); reversi.setMsg(reversi.getStone()+"のターンです"); } static public void turnStone(int x, int y, Reversi reversi) { // フラグの初期化 reversi.setOk_reverse(false); // 8方向の駒の配置を確認し、ひっくり返す turnLeftUp(x, y, reversi); turnUp(x, y, reversi); turnRightUp(x, y, reversi); turnLeft(x, y, reversi); turnRight(x, y, reversi); turnLeftDown(x, y, reversi); turnDown(x, y, reversi); turnRightDown(x, y, reversi); } static public void turnLeftUp(int x, int y, Reversi reversi) { if (y > 1 && x > 1) { // となりの駒 String next = reversi.getBoard(y-1,x-1); // となりの駒が裏駒の場合 if (next.equals(reversi.getRev_stone())) { // さらにその一つとなりから順に確認 for (int i = 2; true; i++) { if (x - i < 0 || y - i < 0 || reversi.getBoard(y-i,x-i).equals(Reversi.EMPTY)) { // 駒がない場合終了 break; } else if (reversi.getBoard(y-i,x-i).equals(reversi.getStone())) { // 自駒の場合 // あいだの駒をすべて自駒にひっくりかえす for (int t = 1; t < i; t++) { // 配列の要素を上書き reversi.setBoard(y-t, x-t, reversi.getStone()); } reversi.setOk_reverse(true); break; } } } } } static public void turnUp(int x, int y, Reversi reversi) { if (y > 1) { // となりの駒 String next = reversi.getBoard(y - 1,x); // となりの駒が裏駒の場合 if (next.equals(reversi.getRev_stone())) { // さらにその一つとなりから順に確認 for (int i = 2; true; i++) { if (y - i < 0 || reversi.getBoard(y - i,x).equals(Reversi.EMPTY)) { // 駒がない場合終了 break; } else if (reversi.getBoard(y - i,x).equals(reversi.getStone())) { // 自駒の場合 // あいだの駒をすべて自駒にひっくりかえす for (int t = 1; t < i; t++) { // 配列の要素を上書き reversi.setBoard(y - t,x,reversi.getStone()); } reversi.setOk_reverse(true); break; } } } } } static public void turnRightUp(int x, int y, Reversi reversi) { if (y > 1 && x < 6) { // となりの駒 String next = reversi.getBoard(y - 1,x + 1); // となりの駒が裏駒の場合 if (next.equals(reversi.getRev_stone())) { // さらにその一つとなりから順に確認 for (int i = 2; true; i++) { if (x + i > 7 || y - i < 0 || reversi.getBoard(y - i,x + i).equals(Reversi.EMPTY)) { // 駒がない場合終了 break; } else if (reversi.getBoard(y - i,x + i).equals(reversi.getStone())) { // 自駒の場合 // あいだの駒をすべて自駒にひっくりかえす for (int t = 1; t < i; t++) { // 配列の要素を上書き reversi.setBoard(y - t,x + t,reversi.getStone()); } reversi.setOk_reverse(true); break; } } } } } static public void turnDown(int x, int y, Reversi reversi) { if (y < 6) { // となりの駒 String next = reversi.getBoard(y + 1,x); // となりの駒が裏駒の場合 if (next.equals(reversi.getRev_stone())) { // さらにその一つとなりから順に確認 for (int i = 2; true; i++) { if (y + i > 7 || reversi.getBoard(y + i,x).equals(Reversi.EMPTY)) { // 駒がない場合終了 break; } else if (reversi.getBoard(y + i,x).equals(reversi.getStone())) { // 自駒の場合 // あいだの駒をすべて自駒にひっくりかえす for (int t = 1; t < i; t++) { // 配列の要素を上書き reversi.setBoard(y + t,x,reversi.getStone()); } reversi.setOk_reverse(true); break; } } } } } static public void turnRight(int x, int y, Reversi reversi) { if (x < 6) { // となりの駒 String next = reversi.getBoard(y,x + 1); // となりの駒が裏駒の場合 if (next.equals(reversi.getRev_stone())) { // さらにその一つとなりから順に確認 for (int i = 2; true; i++) { if (x + i > 7 || reversi.getBoard(y,x + i).equals(Reversi.EMPTY)) { // 駒がない場合終了 break; } else if (reversi.getBoard(y,x + i).equals(reversi.getStone())) { // 自駒の場合 // あいだの駒をすべて自駒にひっくりかえす for (int t = 1; t < i; t++) { // 配列の要素を上書き reversi.setBoard(y,x + t,reversi.getStone()); } reversi.setOk_reverse(true); break; } } } } } static public void turnLeftDown(int x, int y, Reversi reversi) { if (y < 6 && x > 1) { // となりの駒 String next = reversi.getBoard(y + 1,x - 1); // となりの駒が裏駒の場合 if (next.equals(reversi.getRev_stone())) { // さらにその一つとなりから順に確認 for (int i = 2; true; i++) { if (x - i < 0 || y + i > 7 || reversi.getBoard(y + i,x - i).equals(Reversi.EMPTY)) { // 駒がない場合終了 break; } else if (reversi.getBoard(y + i,x - i).equals(reversi.getStone())) { // 自駒の場合 // あいだの駒をすべて自駒にひっくりかえす for (int t = 1; t < i; t++) { // 配列の要素を上書き reversi.setBoard(y + t,x - t,reversi.getStone()); } reversi.setOk_reverse(true); break; } } } } } static public void turnLeft(int x, int y, Reversi reversi) { if (x > 1) { // となりの駒 String next = reversi.getBoard(y,x - 1); // となりの駒が裏駒の場合 if (next.equals(reversi.getRev_stone())) { // さらにその一つとなりから順に確認 for (int i = 2; true; i++) { if (x - i < 0 || reversi.getBoard(y,x - i).equals(Reversi.EMPTY)) { // 駒がない場合終了 break; } else if (reversi.getBoard(y,x - i).equals(reversi.getStone())) { // 自駒の場合 // あいだの駒をすべて自駒にひっくりかえす for (int t = 1; t < i; t++) { // 配列の要素を上書き reversi.setBoard(y,x - t,reversi.getStone()); } reversi.setOk_reverse(true); break; } } } } } static public void turnRightDown(int x, int y, Reversi reversi) { if (y < 6 && x < 6) { // となりの駒 String next = reversi.getBoard(y + 1,x + 1); // となりの駒が裏駒の場合 if (next.equals(reversi.getRev_stone())) { // さらにその一つとなりから順に確認 for (int i = 2; true; i++) { if (x + i > 7 || y + i > 7 || reversi.getBoard(y + i,x + i).equals(Reversi.EMPTY)) { // 駒がない場合終了 break; } else if (reversi.getBoard(y + i,x + i).equals(reversi.getStone())) { // 自駒の場合 // あいだの駒をすべて自駒にひっくりかえす for (int t = 1; t < i; t++) { // 配列の要素を上書き reversi.setBoard(y + t,x + t,reversi.getStone()); } reversi.setOk_reverse(true); break; } } } } } }
6. フィルタの作成
セッションが無ければ初期設定を行うフィルターです。
勉強のためにフィルタ使ってみたい!ってだけで追加したのでたいして意味のないファイルです。
ポイント
- index.jspを初めて開いた時に盤面の初期設定を行う
Filter.java
package filters; import java.io.IOException; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import models.Logic; import models.Reversi; /** * Servlet Filter implementation class Filter */ @WebFilter("/index.jsp") public class Filter implements javax.servlet.Filter { /** * Default constructor. */ public Filter() { // TODO Auto-generated constructor stub } /** * @see Filter#destroy() */ public void destroy() { // TODO Auto-generated method stub } /** * @see Filter#doFilter(ServletRequest, ServletResponse, FilterChain) */ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // TODO Auto-generated method stub // place your code here HttpSession session = ((HttpServletRequest)request).getSession(); if((Reversi) session.getAttribute("reversi") == null){ // セッションが無ければ、初期設定を行う Reversi reversi = new Reversi(); Logic.initialize(reversi); session.setAttribute("reversi", reversi); } // pass the request along the filter chain chain.doFilter(request, response); } /** * @see Filter#init(FilterConfig) */ public void init(FilterConfig fConfig) throws ServletException { // TODO Auto-generated method stub } }
プログラムは以上になります。
感想
仕事でJavaを使っているので簡単にできると思ったのに、やってみると大変でした。実際にゼロから開発してみると、一度勉強したことはあるけど覚えていないことばかりです。仕事では改修しかやっていないので、コードは読めても自分での開発はできないことがわかりました。しかし、勉強し直しながら開発を進めている感覚で、大変でもあり楽しくもあり充実感はありました。