2010年3月4日木曜日

[習作][GAE] GAEでオセロ対戦アプリ(になったらいいな) (3)

[習作][GAE] GAEでオセロ対戦アプリ(になったらいいな) (2) の続き。

前回 のRESTサービスと通信するクライアント(jQuery(RESTクライアント部) + <canvas>要素(盤面の描画))のプロトを作成たので途中経過メモ。

画面は、対戦一覧と対戦画面の2画面のみ。以下、スクリーンショット。

①ゲームのリスト(ログインユーザがプレイヤーになっている対戦のみ表示)


②対戦中の盤面


http://mocobeta2.appspot.com/
で稼働中。なにかの暇つぶしに・・・ならないと思うけど公開しておく。
  • アクセスにはGoogleアカウントが必要です。
  • テキストボックスに対戦相手のGoogle Nickname(自分でもOK)を入力して「対戦」ボタンで新規ゲームが開始しします。
  • 新規ゲームが作成されると、リストに表示されます。左端の数字(ID)をクリックすると対戦画面へ。
  • 自分のターンでない間は、5秒おきに更新&再描画がかかります(うざったいですが・・・)。
  • Firefox 3.6で動作確認済み。Chromeだと一部動かない・・・ orz
  • たまに反応しなくなる場合は、ページをリロードすると直るようです。^^;


以下、誰も知りたがらない HTML(JSP) + JavaScript のソース(JSPは、Googleアカウントのユーザ情報をセッションに格納するためだけに使用)。

// othello.jsp (インデックスページ、一覧画面)

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ page import="com.google.appengine.api.users.User" %>
<%@ page import="com.google.appengine.api.users.UserService" %>
<%@ page import="com.google.appengine.api.users.UserServiceFactory" %>
<%@ page import="javax.jdo.PersistenceManager"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Othello</title>
<script type="text/javascript" src="./js/jquery.min.js"></script>
<script type="text/javascript">
$(document).ready(function(){
loadGames();
$('#newgame').click(newGame);
$('#opposite').click(clearText);
});
function loadGames(){
$('#matches tr').remove();
//$('#matches').append('<tr><th>ID</th><th>先手</th><th>後手</th><th>STATUS</th><th></th><th></th></tr>');
$.getJSON(
'/resources/matches/mymatches',
function(json){
if(json == null) return false;
$.each(json.matches, function(idx,elem){
var status = '';
var disabled = '';
if (elem.status == 'ongoing') {
status = '対戦中';
} else if (elem.status == 'cancelled') {
status = '中止';
disabled = 'disabled';
} else if (elem.status == 'finished') {
status = '終了';
disabled = 'disabled';
}
var tableRow = '<tr><td class="id"><a href="match.jsp?gameId=' + elem.id + '">' + elem.id + '</a></td><td>' + elem.player1 + ' VS ' + elem.player2
+ '</td><td>' + status + '</td><td><a href="#" class="cancel" ' + disabled + '>中止</a></td><td><a href="#" class="delete">削除</a></td></tr>';
$('#matches').append(tableRow);
});
$('td.id a').html(function(idx,elem){
var id = elem;
$('a.cancel').get(idx).onclick = cancelGameFunc(id);
$('a.delete').get(idx).onclick = deleteGameFunc(id);

return id;
});
}
);
}
function newGame(event){
var opposite = $('#opposite').val();
if (opposite == '' || opposite == '(Google Nickname)') {
alert('対戦相手が指定されていません.');
} else {
$.ajax({
type: "POST",
url: "/resources/matches",
data: {opposite: opposite},
success: function(data,stat,req){ loadGames(); },
error: function(req,stat,error){ 'ERROR: ' + stat; }
});
}
}

function clearText(event) {
//var opposite = $('#opposite').val();
if (this.value == '(Google Nickname)') {
this.value = '';
}
}
function cancelGameFunc(id){
return function(){
$.ajax({
type: "PUT",
url: "/resources/matches/" + id + "/cancel",
success: function(data,stat,req){ loadGames(); },
error: function(req,stat,error){ 'ERROR: ' + stat; }
});
}
}
function deleteGameFunc(id){
return function(event){
$.ajax({
type: "DELETE",
url: "/resources/matches/" + id,
success: function(data,stat,req){ loadGames(); },
error: function(req,stat,error){ 'ERROR: ' + stat; }
});
}
}
</script>
<style type="text/css">
body { font-size: small; }
</style>
</head>
<body>
<%
UserService userService = UserServiceFactory.getUserService();
User user = userService.getCurrentUser();
if (user != null) {
session.setAttribute("userId", user.getNickname());
}
%>

<p>
ようこそ、<%= user.getNickname() %>さん。
</p>

<table id="matches">
</table>

<br/>
☆新規ゲーム開始☆<br/>
<input type="text" id="opposite" size="20" value="(Google Nickname)"> と
<button type="button" id="newgame">対戦</button>

</body>
</html>


// match.jsp (対戦画面)

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" %>
<%@ page import="com.google.appengine.api.users.User" %>
<%@ page import="com.google.appengine.api.users.UserService" %>
<%@ page import="com.google.appengine.api.users.UserServiceFactory" %>

<% String gameId = request.getParameter("gameId"); %>
<%
UserService userService = UserServiceFactory.getUserService();
User user = userService.getCurrentUser();
if (user != null) {
session.setAttribute("userId", user.getNickname());
}
%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<!-- meta HTTP-EQUIV="Refresh" Content="10">
<meta HTTP-EQUIV="Pragma" CONTENT="no-cache"-->
<script type="text/javascript" src="./js/jquery.min.js"></script>
<script type="text/javascript">
$(document).ready(function(){
//drawBoard();
getGame();
$('#pass_b').click(pass);
});
var board;
var candidates;
function getGame() {
$('#canvas').remove();
$('#container').append('<canvas id="canvas" width="300" height="300"></canvas>');
$.getJSON(
'/resources/matches/' + <%= gameId %>,
function(json){
candidates = json.candidates;
$('#gameid').html(json.id);
$('#player1').html(json.player1);
$('#player2').html(json.player2);
if(json.nextPlayer != null) {
var you = '';
if(json.nextPlayer == '<%= user.getNickname() %>') {
you = '(あなた)';
}
if(json.nextColor == '1') {
$('#next').html('黒' + you + 'の番です。');
} else if (json.nextColor == '2') {
$('#next').html('白' + you + 'の番です。');
}
if(json.nextPlayer == '<%= user.getNickname() %>') {
$('#next').append(' 石を置きたいマスをダブルクリック。<button id="hint" type="button">ヒント</button>');
$('#hint').click(show_hint);
}
}
if(json.nextPlayer == '<%= user.getNickname() %>') {
$('#container').dblclick(put);
}
$('#blackCount').html(json.blackCount);
$('#whiteCount').html(json.whiteCount);
var status = '';
if (json.status == 'ongoing') {
status = '対戦中 : ' + json.turn + '手目';
} else if (json.status == 'cancelled') {
status = '中止';
} else if (json.status == 'finished') {
status = '終了';
if(json.blackCount > json.whiteCount) {
$('#winner').html(json.player1 + ' の勝ち!');
} else if (json.whiteCount > json.blackCount) {
$('#winner').html(json.player2 + ' の勝ち!');
} else {
$('#winner').html('引き分け');
}
}
$('#status').html(status);
board = json.board;
drawBoard();
for (i = 0; i < 10; i++) {
for(j = 0; j < 10; j++) {
drawCell(i,j);
}
}
// pass かどうか
if(json.status == 'finished' || json.status == 'cancelled' || json.candidates) {
$('#pass').removeClass('show');
$('#pass').addClass('hide');
} else {
$('#pass').removeClass('hide');
$('#pass').addClass('show');
}

if(json.status == 'ongoing' && json.nextPlayer != '<%= user.getNickname() %>') {
setTimeout('getGame()',5000);
}

}
);
}

// render
function drawBoard() {
for(i = 1; i < 9; i++) {
for(j = 1; j < 9; j++) {
drawBoardPanel(i,j);
}
}
}
function drawBoardPanel(row,col) {
/* canvas要素のノードオブジェクト */
var canvas = document.getElementById('canvas');
/* canvas要素の存在チェックとCanvas未対応ブラウザの対処 */
if ( ! canvas || ! canvas.getContext ) {
return false;
}
/* 2Dコンテキスト */
var ctx = canvas.getContext('2d');
ctx.lineWidth = 2.0;
ctx.fillStyle = 'green';
var x = col * 30;
var y = row * 30;
ctx.beginPath();
ctx.fillRect(x,y,30,30);
ctx.strokeRect(x,y,30,30);
}
function drawHintCell(row,col) {
/* canvas要素のノードオブジェクト */
var canvas = document.getElementById('canvas');
/* canvas要素の存在チェックとCanvas未対応ブラウザの対処 */
if ( ! canvas || ! canvas.getContext ) {
return false;
}
/* 2Dコンテキスト */
var ctx = canvas.getContext('2d');
ctx.globalAlpha = 0.7;
ctx.fillStyle = 'yellow';
var x = col * 30 + 5;
var y = row * 30 + 5;
ctx.beginPath();
ctx.fillRect(x,y,20,20);
//drawCircle(row,col,'yellow');
}
function drawCell(row,col) {
var cell = board[i * 10 + j];
if (cell >= 0) {
if (cell == '1') {
drawCircle(i,j,'black');
} else if (cell == '2') {
drawCircle(i,j,'white');
}
}
}
function drawCircle(row,col,color) {
/* canvas要素のノードオブジェクト */
var canvas = document.getElementById('canvas');
/* canvas要素の存在チェックとCanvas未対応ブラウザの対処 */
if ( ! canvas || ! canvas.getContext ) {
return false;
}
/* 2Dコンテキスト */
var ctx = canvas.getContext('2d');
ctx.fillStyle = color;
ctx.beginPath();
var x = col * 30 + 15;
var y = row * 30 + 15;
ctx.arc(x,y,10,0,Math.PI*2,false)
ctx.fill();
//ctx.restore();
}

function put(event){
var x = event.pageX - 30;
var y = event.pageY - 30;
var row = Math.ceil(y / 30);
var col = Math.ceil(x / 30);
//alert(row + ' ' + col);
$.ajax({
type: "PUT",
url: "/resources/matches/" + <%= gameId %>,
data: {row: row, col: col},
success: function(data,stat,req){ getGame(<%= gameId %>); }
//error: function(req,stat,error){ alert('ERROR! ' + stat + ' そこには置けません。'); }
});
}

function pass(event) {
$.ajax({
type: "PUT",
url: "/resources/matches/" + <%= gameId %>,
data: {pass: "true"},
success: function(data,stat,req){ getGame(<%= gameId %>); },
error: function(req,stat,error){ alert('パスできません。'); }
});
}

function show_hint(event) {
if (candidates == null) return false;
for(i = 0; i < candidates.length; i++) {
var cand = candidates[i];
drawHintCell(cand.row,cand.col);
}
}
</script>
<style type="text/css">
body { font-size: small; }
.show { display: block; }
.hide { display: none; }
.winner {
font-weight: bold;
color: red;
}
</style>
<title>Othello</title>
</head>
<body>

<div id="container"></div>

<div style="margin-left:30px">
Game ID : <span id="gameid"></span> (<span id="status"></span>)<br/>
<span id="player1" style="font-weight:bold"></span> vs <span id="player2" style="font-weight:bold"></span> <br/>
黒 : <span id="blackCount"></span>個 &nbsp;&nbsp;&nbsp;&nbsp;
白 : <span id="whiteCount"></span>個<br/>
<span id="next"></span>
<div id="pass" class="hide">
置くところがありません!<button type="button" id="pass_b">パス</button>
</div>
<div id="winner" style="font-weight:bold; color:red;">
</div>
<a href="othello.jsp">戻る</a>
</div>

</body>
</html>


これをベースに jQuery のプラグインを入れて華やかにしてみたり、CPU対戦できるようにしてみたり、某ガジェットにしてみたり、、、できるといいのだけれど。

0 件のコメント:

コメントを投稿