我正在参加「掘金·启航计划」
现在开发都是框架,偶尔也要“复古”一下,就当复习一下基础了╰( ̄▽ ̄)╭
首先是页面布局
<div class="container">
<div class="players">
<div id="p1"><span class="caret" hidden></span></div>
<div class="vs"></div>
<div id="p2"><span class="caret" hidden></span></div>
</div>
<div class="playground">
<div class="row">
<div class="square" data-index="0"></div>
<div class="square" data-index="1"></div>
<div class="square" data-index="2"></div>
</div>
<div class="row">
<div class="square" data-index="3"></div>
<div class="square" data-index="4"></div>
<div class="square" data-index="5"></div>
</div>
<div class="row">
<div class="square" data-index="6"></div>
<div class="square" data-index="7"></div>
<div class="square" data-index="8"></div>
</div>
<div class="overlay">
<button class="btn start">Start</button>
<div class="winner" hidden>
<img class="badge" src="images/win.svg" alt="Winner">
<div class="logo"></div>
</div>
<div class="draw">
</div>
</div>
</div>
<div class="controls">
<button class="btn reset">Reset</button>
</div>
</div>
然后编写一下样式
html {
font-size: 10px;
}
body {
margin: 0;
background: #f8f8f8;
}
a {
color: #0d77cc;
text-decoration: none;
}
[hidden] {
display: none !important;
}
.container {
width: 27rem;
margin: auto;
padding: 2rem 0;
}
.players {
width: 27rem;
margin: auto;
position: relative;
}
.row {
border-bottom: 1px solid #ccc;
}
.players, .row {
display: flex;
}
.players > div {
flex: 1;
height: 9rem;
background-size: 80%;
background-position: 50%;
background-repeat: no-repeat;
}
.players .vs {
background-size: 60%;
background-image: url('../images/vs.png');
}
.players #p1, .players, #p2 {
position: relative;
}
.players .caret {
top: -3px;
left: 3.5rem;
position: absolute;
opacity: .85;
border-top: 1rem solid #555;
border-left: 1rem solid transparent;
border-right: 1rem solid transparent;
}
.players .vue, .square.vue {
background-size: 78%;
background-image: url('../images/vue.png');
}
.players .react {
background-size: 83%;
background-image: url('../images/react.png');
}
.playground {
margin: 10px 0;
border-radius: 2px;
background: #fff;
position: relative;
border: 1px solid #ddd;
box-shadow: 0 0 2px #ccc;
-webkit-tap-highlight-color: rgba(0,0,0,0);
}
.playground .overlay {
top: 0;
left: 0;
right: 0;
bottom: 0;
overflow: hidden;
position: absolute;
display: flex;
transform: scale(1);
transition: all .5s ease;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, .9);
}
.playground .overlay.minimize {
transform: scale(0);
}
.playground .overlay .draw {
display: none;
width: 100%;
height: 100%;
background: url(../images/draw.jpg) no-repeat;
background-size: cover;
z-index: 2;
opacity: .1;
}
.overlay .draw.clean {
opacity: .7;
}
.square {
flex: 1;
height: 9rem;
cursor: pointer;
background-size: 80%;
background-position: 50%;
background-repeat: no-repeat;
border-right: 1px solid #ccc;
}
.square:last-child {
border-right: 0;
}
.square.vue {
background-size: 70%;
}
.square.react {
background-image: url('../images/react.png');
background-size: 72%;
}
.controls {
margin-top: 3rem;
margin-bottom: 2rem;
text-align: center;
}
.winner {
z-index: 10;
top: -1rem;
position: relative;
}
.winner .badge {
width: 10rem;
margin: auto;
display: block;
}
.winner .logo {
width: 15rem;
height: 15rem;
top: -1rem;
position: relative;
background-size: contain;
background-repeat: no-repeat;
}
.winner.vue .logo {
background-image: url('../images/vue.png');
}
.winner.react .logo {
background-image: url('../images/react.png');
}
.btn {
width: 100px;
height: 36px;
border: 0;
outline: 0;
color: #fff;
cursor: pointer;
font-size: 16px;
font-weight: bold;
border-radius: 4px;
letter-spacing: 1px;
-webkit-appearance: none;
box-shadow: 0 0 1px #ccc;
}
.btn[disabled] {
background: #ccc !important;
cursor: not-allowed !important;
}
.btn.start {
z-index: 10;
background: #23d87c;
}
.btn.reset {
background: #ff9800;
}
看一下最终的样子
只是个简单的小游戏,HTML和CSS部分比较简单,没什么好说的,咱们重点来理一下JS部分
首先是开始和重置两个按钮,逻辑比较简单,咱们先来实现
function Game(el) {
this.$el = el;
this.state = 'init';
this.p1 = new Player(document.querySelector('#p1'));
this.p2 = new Player(document.querySelector('#p2'));
this.$winner = this.$el.querySelector('.winner');
this.$overlay = this.$el.querySelector('.overlay');
this.$playground = this.$el.querySelector('.playground');
this.$draw = this.$el.querySelector('.draw');
this.$start = this.$el.querySelector('.btn.start');
this.$reset = this.$el.querySelector('.btn.reset');
this.$reset.disabled = true;
this.$start.addEventListener('click', this.onClickStart.bind(this));
this.$reset.addEventListener('click', this.onClickReset.bind(this));
this.$playground.addEventListener('click', this.onClickSquare.bind(this));
var $squares = [].slice.call(this.$el.querySelectorAll('.square'));
this.squares = $squares.map(function (element) {
return new Square(element);
});
}
Game.prototype.start = function () {
this.p1.setActive(true);
this.p2.setActive(false);
this.$overlay.hidden = true;
this.$start.hidden = true;
this.$reset.disabled = false;
this.$draw.style.display = 'none';
this.state = 'start';
};
Game.prototype.reset = function () {
this.resetSquares();
this.p1.setActive(false);
this.p2.setActive(false);
this.$winner.hidden = true;
this.$winner.className = 'winner';
this.$overlay.hidden = false;
this.$start.hidden = false;
this.$reset.disabled = true;
this.$draw.style.display = 'none';
this.$draw.classList.remove('clean');
this.state = 'init';
};
Game.prototype.onClickStart = function (e) {
this.start();
};
Game.prototype.onClickReset = function (e) {
this.reset();
};
开始之后,自然要实现双方棋手交替下棋,并在顶部标记当前的下棋方,如下图
同时要保证,点击棋盘时,显示的是当前的棋手的棋子
function Player(el) {
this.active = false;
this.$el = el;
this.name = this.$el.className;
this.$caret = this.$el.querySelector('.caret');
}
Player.prototype.setActive = function (active) {
this.active = !!active;
this.$caret.hidden = !active;
};
Game.prototype.onClickSquare = function (e) {
if (!e.target.matches('.square')) return;
if (this.state !== 'start') return;
if (e.target.classList.length > 1) return;
this.squares[e.target.dataset.index].set(
this.activePlayer().name,
this.p1.active ? 1 : -1
);
var winner = this.getWinner();
if (winner) {
this.showWinner(winner);
return;
}
this.isDraw();
this.switchPlayer();
};
Game.prototype.activePlayer = function () {
return this.p1.active ? this.p1 : this.p2;
};
Game.prototype.switchPlayer = function () {
this.p1.setActive(!this.p1.active);
this.p2.setActive(!this.p2.active);
};
效果如下图所示
解决完棋手轮流点击及棋盘显示棋子的问题,就是要判断输赢了
我们将9宫格的棋盘看成以下的样子,每一格都有一个对应的数字
然后我们就可以罗列出,所有的胜利结果的数组,同时进行输赢的判断
Game.prototype.calcWinValues = function () {
var wins = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
];
var result = [];
for (var i = 0; i < wins.length; i++) {
var val =
this.squares[wins[i][0]].val +
this.squares[wins[i][1]].val +
this.squares[wins[i][2]].val;
result.push(val);
}
return result;
};
Game.prototype.getWinner = function () {
var values = this.calcWinValues();
if (
values.find(function (v) {
return v === 3;
})
)
return this.p1;
if (
values.find(function (v) {
return v === -3;
})
)
return this.p2;
};
效果如下图所示
当然还要考虑到平手的情况
Game.prototype.isDraw = function () {
if (
!this.squares.find(function (s) {
return s.val === 0;
})
) {
this.$overlay.hidden = false;
this.$draw.style.display = 'block';
var self = this;
setTimeout(function () {
self.$draw.classList.add('clean');
}, 400);
}
};
效果如下图所示
最后,既然是面向对象的方式,自然要new一个实例啦
document.addEventListener('DOMContentLoaded', function () {
window.game = new Game(document.querySelector('.container'));
});