HTML5 /JavaScript Tic-Tac-Toe

В качестве упражнения я решил создать простую игру Tic-Tac-Toe. Это Ruby on Rails, но на данный момент я не использую серверную часть для чего-либо (я собираюсь наращивать ее в будущем, хотя).

Поскольку я довольно новичок в JavaScript, HTML5 и CSS, мне бы хотелось получить некоторые отзывы о том, что я сделал неправильно или что можно сделать лучше.

index.html.erb

<!DOCTYPE html>

<div id="container">
  <div class="row">
    <canvas class="field" id="f11" width="150" height="150" token="None"></canvas>
    <canvas class="field" id="f12" width="150" height="150" token="None"></canvas>
    <canvas class="field" id="f13" width="150" height="150" token="None"></canvas>
  </div>
  <div class="row">
    <canvas class="field" id="f21" width="150" height="150" token="None"></canvas>
    <canvas class="field" id="f22" width="150" height="150" token="None"></canvas>
    <canvas class="field" id="f23" width="150" height="150" token="None"></canvas>
  </div>
  <div class="row">
    <canvas class="field" id="f31" width="150" height="150" token="None"></canvas>
    <canvas class="field" id="f32" width="150" height="150" token="None"></canvas>
    <canvas class="field" id="f33" width="150" height="150" token="None"></canvas>
  </div>
  <div class="row">
    <canvas id="reset" width="150" height="50"></canvas>
  </div>
</div>

tic_tac_toe.js

var tokenAttributeName = 'token';
var idAttributeName = 'id';

var circleTokenName = 'Circle';
var crossTokenName = 'Cross';
var noTokenName = 'None';

var circleColor = 'green';
var crossColor = 'red';
var fontColor = 'black';
var font = "bold 30px Helvetica";

var resetText = "Reset";

var hoverOnOpacity = 1.0;
var hoverOffOpacity = 0.75;
var highlightTime = 100;

var turn = circleTokenName;
var won = false;

$(document).ready(function() {
  drawResetCanvas();

  $('canvas#reset').click(function(e) {
    location.reload();
  });

  $('canvas').hover(
    function(e) {
      var field = $(this);

      field.animate({ opacity: hoverOnOpacity }, highlightTime);
    },
    function(e) {
      var field = $(this);

      field.animate({ opacity: hoverOffOpacity }, highlightTime);
  });

  $('canvas.field').click(function(e) {
    var field = $(this);
    var fieldId = field.attr(idAttributeName);

    if (field.attr(tokenAttributeName) != noTokenName || won) {
      return;
    }

    if (turn == circleTokenName) {
      drawCircle(fieldId);
      field.attr(tokenAttributeName, circleTokenName);
    }
    else {
      drawCross(fieldId);
      field.attr(tokenAttributeName, crossTokenName);
    }

    //field.animate({opacity: 1.0}, 0, function() { field.animate({opacity:0.75},2000)});

    if (checkWin(turn)) {
      alert(turn + ' won!');
      won = true;
    }
    else if (checkDraw()) {
      alert('Draw!');
    };

    turn = turn == circleTokenName ? crossTokenName : circleTokenName;
  });
});

function checkWin(figure) {
  return (
    checkRows(figure) ||
    checkCols(figure) ||
    checkDiagonals(figure)
  );
};

function checkRows(figure) {
  return (
    checkRow(1, figure) ||
    checkRow(2, figure) ||
    checkRow(3, figure)
  );
};

function checkRow(rowId, figure) {
  return (
    checkField(rowId, 1, figure) &&
    checkField(rowId, 2, figure) &&
    checkField(rowId, 3, figure)
  );
};

function checkCols(figure) {
  return (
    checkCol(1, figure) ||
    checkCol(2, figure) ||
    checkCol(3, figure)
  );
};

function checkCol(colId, figure) {
  return (
    checkField(1, colId, figure) &&
    checkField(2, colId, figure) &&
    checkField(3, colId, figure)
  );
};

function checkDiagonals(figure) {
  return (
    checkField(1, 1, figure) &&
    checkField(2, 2, figure) &&
    checkField(3, 3, figure)
  ) ||
  (
    checkField(1, 3, figure) &&
    checkField(2, 2, figure) &&
    checkField(3, 1, figure)
  );
};

function checkDraw() {
  return !(
    checkField(1, 1, noTokenName) ||
    checkField(1, 2, noTokenName) ||
    checkField(1, 3, noTokenName) ||
    checkField(2, 1, noTokenName) ||
    checkField(2, 2, noTokenName) ||
    checkField(2, 3, noTokenName) ||
    checkField(3, 1, noTokenName) ||
    checkField(3, 2, noTokenName) ||
    checkField(3, 3, noTokenName)
  );
};

function checkField(rowId, colId, figure) {
  return $('canvas#f' + rowId + colId).attr(tokenAttributeName) == figure
}

function drawCircle(fieldId) {
  var canvas =  document.getElementById(fieldId);
  var context = canvas.getContext('2d');
  var centerX = canvas.width / 2;
  var centerY = canvas.height / 2;
  var innerRadius = 0.5 * canvas.width / 2;
  var outerRadius = 0.75 * canvas.width / 2;

  context.beginPath();
  context.arc(centerX, centerY, outerRadius, 0, 2 * Math.PI, false);
  context.arc(centerX, centerY, innerRadius, 0, 2 * Math.PI, true);
  context.fillStyle = circleColor;
  context.fill();
};

function drawCross(fieldId) {
  var canvas =  document.getElementById(fieldId);
  var context = canvas.getContext('2d');
  var centerX = canvas.width / 2;
  var centerY = canvas.height / 2;

  var innerMostPointsOffset = 0.20 * canvas.width / 2;
  var innerEndingPointsXOffset = 0.4 * canvas.width / 2;
  var innerEndingPointsYOffset = 0.75 * canvas.width / 2;
  var outerEndingPointsXOffset = 0.75 * canvas.width / 2;
  var outerEndingPointsYOffset = 0.75 * canvas.width / 2;

  context.fillStyle = crossColor;
  context.beginPath();
  context.moveTo(centerX - innerMostPointsOffset, centerY);
  context.lineTo(centerX - outerEndingPointsXOffset, centerY - outerEndingPointsYOffset);
  context.lineTo(centerX - innerEndingPointsXOffset, centerY - innerEndingPointsYOffset);
  context.lineTo(centerX, centerY - innerMostPointsOffset);
  context.lineTo(centerX + innerEndingPointsXOffset, centerY - innerEndingPointsYOffset);
  context.lineTo(centerX + outerEndingPointsXOffset, centerY - outerEndingPointsYOffset);
  context.lineTo(centerX + innerMostPointsOffset, centerY);
  context.lineTo(centerX + outerEndingPointsXOffset, centerY + outerEndingPointsYOffset);
  context.lineTo(centerX + innerEndingPointsXOffset, centerY + innerEndingPointsYOffset);
  context.lineTo(centerX, centerY + innerMostPointsOffset);
  context.lineTo(centerX - innerEndingPointsXOffset, centerY + innerEndingPointsYOffset);
  context.lineTo(centerX - outerEndingPointsXOffset, centerY + outerEndingPointsYOffset);
  context.closePath();
  context.fill();
};

function drawResetCanvas() {
  var canvas = document.getElementById("reset");
  var context = canvas.getContext("2d");
  context.fillStyle = fontColor;
  context.font = font;
  context.fillText(resetText, 35, 35);
};

tic_tac_toe.css

canvas {
  background-color: #DDDDDD;
  width: 150px;
  border: 1px solid white;
  float: left;
  opacity: 0.75;
}

canvas.field {
  height: 150px;
}

div.row {
  width: 456px;
  margin: 0 auto;
}

div#container {
  width: 100%;
}

canvas#reset {
  height: 50px;
  margin-top: 30px;
  margin-left: 152px;
}
11 голосов | спросил Lasooch 5 Maypm14 2014, 20:02:20

2 ответа


7

Во-первых, (правильный) HTML-документ выглядит следующим образом:

<!DOCTYPE html>
<html>
    <head>
        <title>Tic tac toe</title>
    </head>
    <body>
        <!-- Stuff -->
    </body>
</html>

Html, head, body и title настоятельно рекомендуется, tho не требуется .

Кажется, вы часто используете <canvas> s ... почему? Не можете ли вы просто сделать их изображениями?

<div class="row">
    <img class="field" />
    <img class="field" />
    <img class="field" />
</div>
<div class="row">
    <img class="field" />
    <img class="field" />
    <img class="field" />
</div>
<div class="row">
    <img class="field" />
    <img class="field" />
    <img class="field" />
</div>

Холсты уродливые и пиксельные, изображения красивые и потенциально векторные (или, по крайней мере, проще сделать высокое разрешение (важно для устройств сетчатки)). Единственный недостаток - вам нужно создавать изображения ... но я просто сделал это для вас (настраивайте по своему вкусу):

<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" fill="#f00"> <!-- An X -->
    <polygon points="0,4 4,0 50,46 46,50" />
    <polygon points="50,4 4,50 0,46 46,0" />
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" stroke="#090" stroke-width="5" fill="none"> <!-- An O -->
    <circle cx="25" cy="25" r="22" />
</svg>

Не делайте bajillion id s. Вместо $('canvas#f' + rowId + colId):

document.getElementById('container').children[rowId].children[colId]

Да, я ненавижу jQuery. Особенно анимации. Переходы CSS поддерживаются достаточно широко, что вы, вероятно, должны использовать это вместо этого. Он более плавный - DOM не должен быть запутан с каждым 20 мс (как в случае с анимацией jQuery).

Итак, неплохая работа для недавних необозримых настольных браузеров с пользователями, которые не могут заметить медленность jQuery. Теперь просто сделайте его векторным и ванильным и не получите кучу ... вещей в DOM (ids, «токены» (недопустимый атрибут)), которые можно поместить в массивы.

ответил bjb568 5 Maypm14 2014, 23:03:26
3

Я сосредоточусь только на вашем методе, чтобы определить выигрыши /розыгрыши, если вам нужно столько кода, то вы, вероятно, что-то делаете неправильно;)

Учитывая, что tic tac toe - 3 на 3, я считаю, что просто жесткое кодирование является непревзойденным в выражении того, что вы хотите проверить логикой.

Кроме того, для определения ничьей, если вы собираетесь использовать jQuery, вы можете также спросить через jQuery, имеет ли какой-либо элемент значение атрибута noTokenName.

Итак, я бы встретил что-то вроде этого:

function checkWin(figure) {
  var winningTriplets = [
    [ [1,1] , [1,2] , [1,3] ], //Column 1
    [ [2,1] , [2,2] , [2,3] ], //Column 2
    [ [3,1] , [3,2] , [3,3] ], //Column 3
    [ [1,1] , [2,1] , [3,1] ], //Row 1
    [ [1,2] , [2,2] , [3,2] ], //Row 1
    [ [1,3] , [2,3] , [3,3] ], //Row 1
    [ [1,1] , [2,2] , [3,3] ], //Diagonal 1
    [ [1,3] , [2,2] , [3,1] ]  //Diagonal 1
  ];

  for( var i = 0 ; i < winningTriplets.length ; i++ ){
    if( checkTriplet( winningTriplets[i] , figure ) ){
      return true;
    }
  }
};

/* Check 3 coordinates for a given triplet
   a triplet is an array with 3 entries ( arrays ) with 2 entries 
   index 0 is col, index 1 row */
function checkTriplet( triplet , figure) {
  var X = 0, Y = 1;
  return (
    checkField( triplet[0][X], triplet[0][Y] , figure) ||
    checkField( triplet[1][X], triplet[1][Y] , figure) ||
    checkField( triplet[2][X], triplet[2][Y] , figure);    
  );
};

function checkDraw() {
  var queryString = "[" + tokenAttributeName + "='" + noTokenName + "']";
  return !$( queryString ).length
};

function checkField(rowId, colId, figure) {
  return $('canvas#f' + rowId + colId).attr(tokenAttributeName) == figure
}

Самая большая проблема, которую я вижу, заключается в том, что вы используете элементы HTML, чтобы содержать состояние вашей игры. Элементы HTML для этого не предназначены. У вас должен быть объект JS с состоянием игры, а затем с помощью этого объекта вы можете провести игру.

Однако, поскольку это небольшая игра, я думаю, вы можете уйти от своего подхода.

ответил konijn 5 Maypm14 2014, 23:22:30

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132