情報板となるInformationクラスの作成
Informationクラスには次のようなプロパティやメソッドがあります。
名前 | 説明 |
---|
elements | 情報板のtd要素たちの配列 |
createRowElement() | 一行分のtr要素とtd要素たちを作成する |
update() | 情報板の表示を更新する |
Information.jsに次のように書き込みます。
state.colors.forEach((color) => {
this.createRowElement(color);
createRowElement (color) {
// tr要素を作成して、その中にtd要素を入れていく
const trElement = document.createElement('tr');
// 現在のプレイヤーに赤い矢印を表示するためのtd要素
const arrowTdElement = document.createElement('td');
arrowTdElement.style.color = 'red';
arrowTdElement.style.fontSize = '14px';
const stoneTdElement = document.createElement('td');
stoneTdElement.textContent = '●';
stoneTdElement.style.color = color.colorCode;
// 「あなた」や「COM」と表示するためのtd要素
const typeTdElement = document.createElement('td');
const html = color.members.map((player) => player.isHuman() ? '<div>あなた</div>' : '<div>COM</div>');
typeTdElement.innerHTML = html;
const stoneContTdElement = document.createElement('td');
stoneContTdElement.classList.add('num-area');
const winContTdElement = document.createElement('td');
winContTdElement.classList.add('num-area');
// winContTdElement.textContent = 0;
trElement.append(arrowTdElement, stoneTdElement, typeTdElement, stoneContTdElement, winContTdElement);
const bodyElement = document.getElementById('information-body');
bodyElement.append(trElement);
this.elements.push({ arrowTdElement, stoneContTdElement, winContTdElement });
state.colors.forEach((color, i) => {
// 現在のプレイヤーがこの色であれば矢印を表示させる
this.elements[i].arrowTdElement.textContent = color === state.currentPlayer.color ? '▶︎' : '';
this.elements[i].stoneContTdElement.textContent = color.stoneCount;
this.elements[i].winContTdElement.textContent = color.winCount;
情報板のCSSを追加する
styles.cssに次のものを書きくわえます。
box-shadow: 1px 2px 2px 0px #444;
background: radial-gradient(circle farthest-side at 90% 90%, #444 0%, rgb(143, 143, 143) 100%);
メッセージを表示するMessageクラスの作成
Messageクラスには次のようなプロパティやメソッドがあります。
名前 | 説明 |
---|
element | メッセージのdiv要素 |
show() | メッセージを表示する |
hide() | メッセージを非表示にする |
showPass() | 結果時に表示する石のdiv要素を作成する |
createWinningStoneDiv() | 結果時の勝利メッセージを作成する |
createWinMessage() | 結果時の引き分けメッセージを作成する |
createDrawMessage() | 結果のメッセージを表示する |
Message.jsに次のように書き込みます。
this.element = document.getElementById('message');
this.element.style.transitionDuration = `${MESSAGE_DURATION_TIME}ms`;
this.element.style.visibility = 'visible';
this.element.style.opacity = 1;
await sleep(MESSAGE_DURATION_TIME);
this.element.style.visibility = 'hidden';
this.element.style.opacity = 0;
await sleep(MESSAGE_DURATION_TIME);
this.element.textContent = 'PASS';
await sleep(PASS_MESSAGE_TIME);
createWinningStoneDiv (colorCode) {
return `<div class="winning-stone" style="color:${colorCode}">●</div>`;
return `<div>${this.createWinningStoneDiv(state.mostColors[0].colorCode)}の勝ち</div>`;
let drawMessage = '<div>';
state.mostColors.forEach((color) => {
drawMessage += this.createWinningStoneDiv(color.colorCode);
drawMessage += '</div><div>引き分け</div>';
// ゲーム終了が最も多かった石の色が1つだけの場合、勝利メッセージを作成する
// 複数の色がある場合は引き分けメッセージを作成する
let resultMessage = state.mostColors.length === 1
? this.createWinMessage()
: this.createDrawMessage();
resultMessage += '<div class="small-text">クリックしてもう1回!</div>';
this.element.innerHTML = resultMessage;
メッセージのCSSを追加
styles.cssに次のものを書きくわえます。
/* メッセージは画面全体に表示するためfixedにする */
background-color: rgba(30, 30, 30, 0.5);
-webkit-text-stroke-width: 2px;
-webkit-text-stroke-color: #333;
-webkit-text-stroke-width: 1px;
/* text-align: center; */
animation-duration: 500ms;
animation-iteration-count: infinite;
animation-direction: alternate;
/* 画面が大きいときはメッセージの文字も大きくする */
@media (min-width: 700px) {
-webkit-text-stroke-width: 3px;
ゲームの流れ
1ターンごとのゲームの流れは次のようになります。
ゲームを進行するGameクラスの作成
Gameクラスには次のようなプロパティやメソッドがあります。
名前 | 説明 |
---|
board | Boardオブジェクト |
information | Infomationオブジェクト |
message | Messageオブジェクト |
start() | プレイヤーが行動できるようにする |
pass() | パスをする |
flipStones() | 挟んだ石を裏返す |
update() | 挟んだ石を裏返して、ゲームを進行する |
gameover() | ゲーム終了の処理をして結果メッセージを表示する |
reset() | ゲーム状態と盤面を初期状態に戻す |
Game.jsに次のように書き込みます。
this.board = new Board();
this.information = new Information();
this.message = new Message();
this.information.update();
this.board.checkSelectableSquares();
if (state.selectableSquares.length === 0) {
// もし現在のプレイヤーがコンピュータの場合、石を置おくように行動させる
if (state.currentPlayer.isComputer()) {
state.currentPlayer.act();
// 現在のプレイヤーの石が盤面に残っている場合は、PASSメッセージを表示する
if (state.currentPlayer.isAlive()) {
await this.message.showPass();
// すべてのプレイヤーがパスした場合は、ゲームを終了させる
if (state.isGameover()) {
// 1個目は0ミリ秒後、2個目は100ミリ秒後、3個目は200ミリ秒後、・・・となる
state.sandwichedStones.forEach((stone, i) => {
() => { stone.flip(state.currentPlayer.color); },
await sleep(state.sandwichedStones.length * FLIP_INTERVAL_TIME + FLIP_DURATION_TIME);
state.sandwichedStones = [];
this.board.resetSelectableSquares();
if (state.isGameover()) {
this.information.update();
this.message.showResult();
// メッセージをクリックすると初期化するようにする
// bind(this)を付けないと、resetメソッド内のthisはクリックした要素になってしまう
// once: trueとすることで一回のみresetを実行するようにする
this.message.element.addEventListener('click', this.reset.bind(this), { once: true });
コンピュータの戦略を作成
ここではコンピュータの強さレベルを2つ作成します。
強さレベル1
強さレベル2
- 角のマス目に石を置ける場合、優先的に置くようにする
- それ以外の場合、ゲームの序盤は石をなるべく取らないようにして、終盤になったら多く取るようにする
これらの関数をStrategyクラスの静的メソッドとして作ります。
Strategy.jsに次のように書き込みます。
// いずれのメソッドもSquareオブジェクトを返す
static getSquare (lebel) {
return Strategy[lebel]();
return pickRandom(state.selectableSquares);
// 石を置けるマス目の中から角のマス目を抜き出す。無い場合は空の配列となる
const cornerStones = state.selectableSquares.filter((square) => square.isCorner);
// 角のマス目があった場合、その中から一つ選んで返す
if (cornerStones.length !== 0) {
return pickRandom(cornerStones);
// 挟める石の個数の配列を作る。たとえば、[2,1,2,..]のような配列になる
const sandwichStonesCountList = state.selectableSquares.map((square) => square.sandwichStones.length);
// 自分が置くことのできる残りマス目数を計算する
const remainingSquareCount = state.emptySquareCount / state.colors.length;
// 残りマス目数が8個以上あれば最小の個数、それ以外であれば最大の個数を取得する
const sandwichStonesCount = remainingSquareCount >= 8
? Math.min(...sandwichStonesCountList)
: Math.max(...sandwichStonesCountList);
// その個数となるマス目を抜き出す。複数個ある可能性もあるため配列に入れる
const selectedSquares = state.selectableSquares.filter((square) => square.sandwichStones.length === sandwichStonesCount);
return pickRandom(selectedSquares);
mainファイルの作成
main.jsで今まで作ってきたクラスのオブジェクトを生成します。
main.jsに次のように書き込みます。
const state = new State();
これでマルチオセロの完成です!
ブラウザからindex.htmlを開いてみてください。
オリジナルのオセロを作ろう
settings.jsの colorList
に色を追加したり、 initialBoard
を変更しすればオリジナルの盤面を作ることができます。
すべてのプレイヤーをコンピュータにしてコンピュータ同士の戦いを観戦することもできます。そのときはsettings.jsの GAME_SPEED
を低めに設定するとゲーム進行が速くなるので見やすくなります。
また、Strategyクラスに新しい戦略を加えることもできます。石が少ない色を積極的に殲滅しにいく戦略などもあると面白いと思います。
ぜひ自分だけのオセロを作ってみてください。