このオセロの特徴
- 🤼複数人で対戦可能
- 🛠️マス目の形を自由に作れる
- 🧑🤝🧑「ヒトとCOMの混合チーム vs COMチーム」のようなチーム編成もできる
- 📝必要なのはHTML、CSS、Javascriptのみ(画像もライブラリも必要なし!)
このプログラムについて
Javascriptを使いオブジェクト指向で作成しています。なるべくシンプルでわかりやすくするためカプセル化などの技術は使用していません。
このゲームではcanvas要素に絵を描くのではなく、HTML要素とCSSを用いてオセロを表現しています。こうすることでCSSをフルに活用でき、石を裏返すアニメーションなども簡単に実装することができます。
対戦人数やマス目の形、コンピュータの戦略などを簡単に設定できるようにしているので、自分だけのオリジナルのオセロを作ることができます。
必要なプログラミング知識
HTMLとCSSの基礎
Javascriptの基礎(DOMの操作方法やasyncの使い方など)
オブジェクト指向の基礎(クラスやインスタンスの作り方など)
オセロのパーツの呼び方と対応するクラス
各パーツの呼び方は次のようにします。英語名はクラス名や変数名です。
その他のクラスとして次のようなものがあります。
クラス名 | 説明 |
---|
Game | ゲームを進行するクラス |
Message | 「PASS」などのメッセージを表示するクラス |
Player | ゲームプレイヤーのクラス |
State | ターン数など現在のゲームの状態を管理するクラス |
Strategy | コンピュータの戦略を集めたクラス |
ファイルの作成
次のようにreversiフォルダを作成し、その中にHTMLファイル、CSSファイル、JSファイルを作成します。
HTMLの作成
index.htmlに次のように書き込みます。
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" type="text/css" href="styles.css">
<script defer src="settings.js"></script>
<script defer src="utils.js"></script>
<script defer src="Color.js"></script>
<script defer src="Board.js"></script>
<script defer src="Game.js"></script>
<script defer src="Information.js"></script>
<script defer src="Message.js"></script>
<script defer src="Player.js"></script>
<script defer src="Square.js"></script>
<script defer src="State.js"></script>
<script defer src="Stone.js"></script>
<script defer src="Strategy.js"></script>
<script defer src="main.js"></script>
<th class="num-area">石の数</th>
<th class="num-area">勝ち数</th>
<tbody id="information-body">
head要素内のscriptは settings.js
が最初で、 main.js
が最後になるようにしてください。また、scriptに defer
を付けないとHTML解析前にJavascriptを実行してしまうためエラーとなります。
盤面やメッセージはJavascriptで動的に作成していくので空白のままです。
CSSの作成
次にstyles.cssに次のように書き込みます。
background: repeating-linear-gradient(-45deg, #E2E8F0, #E2E8F0 8px, #EDF2F7 8px, #EDF2F7 10px);
情報板と盤面を縦に並べて中央に寄せるようにしています。
styles.cssにはこの後もいくつか追加していきます。
ゲームの初期設定
settings.jsに次のようなゲームの設定を書いていきます。
変数名 | 説明 |
---|
colorList | 石の色とその色を操作するプレイヤーの配列 |
initialBoard | 盤面の初期状態 |
GAME_SPEED | メッセージの表示速度などゲーム進行の速さ |
MAX_COM_WAITING_TIME | コンピュータの最大待機時間(ミリ秒単位) |
PASS_MESSAGE_TIME | メッセージで「PASS」と表示し続ける時間 |
MESSAGE_DURATION_TIME | メッセージ表示のアニメーションにかかる時間 |
FLIP_INTERVAL_TIME | 石を裏返すタイミングの間隔 |
FLIP_DURATION_TIME | 石が完全に裏返るまでにかかる時間 |
色と参加プレイヤーの設定
* 石の色とその色を操作するプレイヤーを配列に設定する。
members: [{ type: 'human' }]
members: [{ type: 'computer', level: 1 }]
members: [{ type: 'computer', level: 1 }]
members: [{ type: 'computer', level: 2 }]
members: [{ type: 'computer', level: 2 }]
colorList
には石の色を書きます。 'white'
や '#ffffff'
など、CSSで認識できる形式で書くようにしてください。
members
にはこの色を操作できるプレイヤーを書きます。ヒトが操作する場合は type: 'human'
にします。
コンピュータが操作する場合は type: 'computer'
にして、強さを level
に設定します。 level
は1と2から選択可能です(あとあと独自にlevelを追加することももできます)
1つの色に複数人のプレイヤーを書くこともできるので、ヒトとコンピュータの混合チームなども作成できます。
盤面の初期配置
盤面の初期配置を二次元配列に数字で書いていきます。
数字 | 説明 |
---|
-2 | 何もない空間 |
-1 | 石の置いていないマス目 |
0以上 | 石の置いてあるマス目(色をcolorListの配列番号で指定する) |
* initialBoardに盤面の初期配置を数字で書いていく
* 0以上:石の置いてあるマス目(色をcolorListの配列番号で指定する)
[-1, -1, -1, -1, -1, -1, -1, -1, -1],
[-1, -1, -2, -1, -1, -1, -2, -1, -1],
[-1, -2, -1, +0, +1, +2, -1, -2, -1],
[-1, -1, -1, +1, +2, +3, -1, -1, -1],
[-1, -1, -1, +2, +3, +4, -1, -1, -1],
[-1, -1, -1, +3, +4, +0, -1, -1, -1],
[-1, -2, -1, +4, +0, +1, -1, -2, -1],
[-1, -1, -2, -1, -1, -1, -2, -1, -1],
[-1, -1, -1, -1, -1, -1, -1, -1, -1],
たとえば数字が「+0」の場所は、 colorList[0]
の色の石が置いてあることを意味します。
0以上の数字に+の符号が付いている理由は石の場所をわかりやすくするためで、付けなくてもかまいません。
時間の設定
* メッセージの表示速度やコンピュータの待機時間などの調整をする
* 基準は1で0に近づくほど速度が速くなり、0にすると瞬時に動作するようになる
const MAX_COM_WAITING_TIME = 2000 * GAME_SPEED;
* メッセージで「PASS」と表示し続ける時間(ミリ秒単位)
const PASS_MESSAGE_TIME = 1000 * GAME_SPEED;
* メッセージ表示のアニメーションにかかる時間(ミリ秒単位)
const MESSAGE_DURATION_TIME = 500 * GAME_SPEED;
const FLIP_INTERVAL_TIME = 80 * GAME_SPEED;
* 石が完全に裏返るまでにかかる時間(ミリ秒単位)
const FLIP_DURATION_TIME = 400 * GAME_SPEED;
MAX_COM_WAITING_TIME
はコンピュータの手番になったときに待機する最大時間です。これがないとコンピュータの手番になるとすぐに石を置いてしまいます。
0ミリ秒~MAX_COM_WAITING_TIME
の間でランダムに待機させることでコンピュータが考えているような演出ができます。
また、上記のような変数名や関数名の上に「/***/」を使ってコメントを書く方法をJSDocといいます。このように書くことでVSCodeなどのエディターで変数や関数を使用するときに説明文も表示されるので便利です。
ユーティリティ関数の作成
まずはいろんな場所で使う便利な関数(ユーティリティ関数)を作っておきます。
名前 | 説明 |
---|
pickRandom(array) | 配列の中から1つの要素をランダムに取り出す |
forEachNest(twoDimensionalArray, callback) | 二次元配列をforEachで回していく |
sleep(milliseconds) | 引数で渡した時間(ミリ秒)だけ待機する |
utils.jsに次のように書いていきます。
const pickRandom = (array) => {
const selectedIndex = Math.floor(Math.random() * array.length);
return array[selectedIndex];
* callback関数の引数には二次元配列の要素、行番号、列番号を受け取る
const forEachNest = (twoDimensionalArray, callback) => {
twoDimensionalArray.forEach((rowArray, rowIndex) => {
rowArray.forEach((item, columnIndex) => {
callback(item, rowIndex, columnIndex);
const sleep = (milliseconds) => {
return new Promise((resolve) => {
setTimeout(resolve, milliseconds);
sleep
は Promise
を使った非同期処理となっています。たとえば1000ミリ秒待機させたい場合は次のように使用します。
const someFunction = async () => {
色を管理するColorクラスの作成
Colorクラスはある色の石の数や勝ち数を管理するクラスです。
Colorクラスには次のようなプロパティやメソッドがあります。
名前 | 説明 |
---|
constructor(colorCode) | コンストラクターにカラーコードと配列番号を受け取る |
colorCode | この色のカラーコード |
members | この色の石を操作できるプレイヤーの配列 |
stoneCount | この色の石の数 |
winCount | この色が勝利した回数 |
getCurrentPlayer() | membersの中で現在のプレイヤーを返す |
Color.jsに次のように書き込みます。
this.colorCode = colorCode;
// 現在が何巡目かを計算する。最初の巡目は0巡目となる
// たとえば、石の色の数が3種類、ターン数が4の場合,
// 7/3=1.33から小数点を切り捨てて、現在は1巡目となる
const round = Math.trunc(state.turnCount / state.colors.length);
// 巡目数をこの色のメンバー数で割った余りが現在のプレイヤーの配列番号となる
// たとえば、メンバー数が2人で5巡目の場合、,
// 5÷2の余りは1となりthis.members[1]が現在のプレイヤーとなる
return this.members[round % this.members.length];
プレイヤーとなるPlayerクラスの作成
Playerクラスには次のようなプロパティやメソッドがあります。
名前 | 説明 |
---|
type | "human"または"computer" |
color | Colorオブジェクト |
level | コンピュータのレベル |
isHuman() | ヒトであるか否かを判別する |
isComputer() | コンピュータであるか否かを判別する |
isAlive() | このプレイヤーの石が盤面に残っているか否かを判別する |
act() | コンピュータに石を置くマス目を選択させる |
Player.jsに次のように書き込みます。
constructor(type, color, level = 1) {
return this.type === 'human';
return this.type === 'computer';
return this.color.stoneCount >= 1;
// 0~MAX_COM_WAITING_TIMEミリ秒の間でランダムに待機させる
// こうすることでコンピュータが思考しているような演出ができる
await sleep(Math.random() * MAX_COM_WAITING_TIME);
// getSquareはマス目(Squareオブジェクト)を返す
const selectedSquare = Strategy.getSquare(this.level);
ゲーム状態を管理するStateクラスの作成
Stateクラスはゲームの状態を管理するクラスです。
Stateクラスには次のようなプロパティやメソッドがあります。
名前 | 説明 |
---|
colors | Colorオブジェクトの配列 |
turnCount | 現在のターン数。次のプレイヤーの番になるとターン数が増える |
currentPlayer | 手番となったPlayerオブジェクト |
selectableSquares | 石を置くことができるマス目(Squareオブジェクト)の配列 |
sandwichedStones | 自分の石で挟み込んだ石(Stoneオブジェクト)の配列 |
passCount | 連続してパスした回数 |
emptySquareCount | 空のマス目の個数 |
mostColors | 最も石の数が多いColorオブジェクトの配列 |
init() | 状態を初期化する |
turnCountUp() | ターン数を増やして現在のプレイヤーを変える |
increasePassCount() | 一つの色で石が統一されたか否かを判別する |
isUnified() | ゲーム終了の状態か否かを判別する |
isGameover() | どの色の石が最も多いかチェックする |
State.jsに次のように書き込みます。
this.emptySquareCount = 0;
// colorListからColorオブジェクトの配列を作る
this.colors = colorList.map(({ colorCode, members }, i) => {
const color = new Color(colorCode, i);
members.forEach(({ type, level }) => {
const player = new Player(type, color, level);
color.members.push(player);
this.currentPlayer = this.colors[0].members[0];
this.selectableSquares = [];
this.sandwichedStones = [];
// 現在のターン数を石の色の種類数で割った余りが現在の色の配列番号となる
// たとえば、ターン数が4、石の色が3種類の場合、
// 4÷3の余りが1のため、this.colors[1]が石の色となる
const currentColor = this.colors[this.turnCount % this.colors.length];
// Colorオブジェクトから現在のプレイヤーを取得する
this.currentPlayer = currentColor.getCurrentPlayer();
// 盤面に残っている色のColorオブジェクトを配列として取得する
let existColors = this.colors.filter((color) => color.stoneCount >= 1);
// もし盤面に残っている石の色が1種類だけなら、その色で統一したことになる
return existColors.length === 1;
// または盤面に残っている石の色が1種類だけの状態
return this.emptySquareCount === 0
|| this.passCount === this.colors.length
// たとえば、[22,17,5,...]のような配列になる
const stoneCountList = this.colors.map((color) => color.stoneCount);
let maxStoneCount = Math.max(...stoneCountList);
// 最も石の数が多かったColorオブジェクトの配列を作る
// たとえば、黒と白の石の数が同じだった場合、二つのColorオブジェクトがmostColorsに入る
this.mostColors = this.colors.filter((color) => color.stoneCount === maxStoneCount);
// 最も多い色が1つだけなら、その色を勝ったことにする
if (this.mostColors.length === 1) {
this.mostColors[0].winCount++;