填字游戏
About 7 min
起初是公司一个小游戏,从立项到开发完成大概5天吧,因为时间有些赶,最开始写的并不是很好,画字格用的是 table。 UI上面小小出入,跟一个朋友说做这个项目后,朋友建议我用 pixijs,利用闲暇时间重写了一遍,下面是源码
HTML
<p class="time-container"><time id="time">00:00</time></p>
<div class="game-container">
<canvas id="game" width="500" height="500"></canvas>
<div class="mask">初始化中...</div>
</div>
<div class="tips-container">
<p class="fl">提示:</p>
<p id="tips" class="oh">赫拉利将告诉你人类是如何从动物变成上帝</p>
</div>
<div class="input-container">
<input type="text" class="game-input" id="game-input" />
<a href="javascript:;" class="game-input-send">确认</a>
</div>
JavaScript
require(["zepto", "pixi", "modal"], function($, pixi, modal) {
// 游戏基础配置
modal.pupup({
content: `
<!-- <img src="http://sunday90.cn/upload/2500872220b52768dd9c4e237579f8bc.jpg" /> -->
<a href="javascript:;" class="btn start-btn">开始计时</a>
`,
mask: true,
success: function(layero) {
const that = this;
$(layero).on("click", ".start-btn", function(e) {
console.log(this);
console.log("开始游戏");
let time = document.querySelector("#time");
Countdown.start(0.2, time, function(time) {
console.log(time);
});
that.close();
});
}
});
const ua = window.navigator.userAgent.toLocaleLowerCase();
const device = {
ios: /iphone/g.test(ua),
android: /android/g.test(ua)
};
const unit_size = { width: 50, height: 50 };
const table = { x: 10, y: 10 };
const gameContainer = document.querySelector(".game-container");
const mask = document.querySelector(".mask");
const input = document.querySelector("#game-input");
const tipsEl = document.querySelector("#tips");
const fontStyle = new PIXI.TextStyle({
fontFamily:
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif',
fontSize: 28,
fontWeight: "500",
fill: ["#333"] // gradient
});
const time = document.querySelector("#time");
// 需要渲染的方格二维数组
const unit_data = [
[
{ y: 0, x: 1 },
{ y: 0, x: 5 },
{ y: 0, x: 6 },
{ y: 0, x: 7 },
{ y: 0, x: 8 }
],
[
{ y: 1, x: 1 },
{ y: 1, x: 3 },
{ y: 1, x: 8 }
],
[
{ y: 2, x: 1 },
{ y: 2, x: 2 },
{ y: 2, x: 3 },
{ y: 2, x: 4 },
{ y: 2, x: 6 },
{ y: 2, x: 7 },
{ y: 2, x: 8 }
],
[
{ y: 3, x: 3 },
{ y: 3, x: 7 }
],
[
{ y: 4, x: 0 },
{ y: 4, x: 1 },
{ y: 4, x: 2 },
{ y: 4, x: 3 },
{ y: 4, x: 6 },
{ y: 4, x: 7 },
{ y: 4, x: 8 },
{ y: 4, x: 9 }
],
[
{ y: 5, x: 1 },
{ y: 5, x: 7 }
],
[
{ y: 6, x: 1 },
{ y: 6, x: 4 },
{ y: 6, x: 5 },
{ y: 6, x: 6 },
{ y: 6, x: 7 }
],
[
{ y: 7, x: 1 },
{ y: 7, x: 4 }
],
[
{ y: 8, x: 1 },
{ y: 8, x: 2 },
{ y: 8, x: 3 },
{ y: 8, x: 4 }
],
[
{ y: 9, x: 0 },
{ y: 9, x: 1 }
]
];
// tips
const tips = {
"21222324": "赫拉利将告诉你人类是如何从动物变成上帝",
"40414243": "对两次世界大战的哲学思考,直面存在的危机",
"81828384": "看萧红写的这书名还以为是一条河的历史",
"9091": "鲁迅先生的短篇小说集,里面有《狂人日记》",
"64656667": "天下合久必分,分久必合",
"46474849": "与达尔文《物种起源论》及哥白尼《天体运行论》并称为人类三大思想革命的经典之作",
"262728": "三位艺术天才的非凡人生,罗曼罗兰著",
"5678": "证明你上过中学的又一个有力佐证,冰心奶奶如是说",
"11121": "我们都是故事外的人——阿尔贝·加缪",
"13233343": "一种追求简单生活的价值观",
"415161718191": "余华手下一本关于记忆的长篇小说",
"647484": "走遍万水千山,泪尽撒哈拉",
"2737475767": "达康书记别低头,皇冠会掉",
"81828": "中国四大名著之一"
};
// 创建 12 * 8 的方格
const unit = []; // 所有单元格 二维数组
const unit_block = []; // 需要渲染的单元格
const selectUnit = { isRow: true, unit: [] }; // 选中后的单元格
// 创建材质
const unitTexture = PIXI.Texture.fromImage("./assets/images/unit.svg");
const unitBlockTexture = PIXI.Texture.fromImage(
"./assets/images/unit-block.svg"
);
const unitBlockActiveTexture = PIXI.Texture.fromImage(
"./assets/images/unit-block-active.svg"
);
// 创建舞台
const container = new PIXI.Container();
container.x = 0;
container.y = 0;
// 倒计时
const Countdown = {
down(callBack, success) {
clearInterval(this.Interval);
callBack(
success === true ? this.countTime * 60 - this.time / 1000 : "时间结束"
);
},
check(v) {
if (v < 10) {
return "0" + v;
} else {
return v;
}
},
start(time, el, callBack) {
this.countTime = time;
var endDate = new Date().getTime() + time * 60 * 1000;
this.Interval = setInterval(() => {
this.time = endDate - new Date().getTime();
if (this.time <= 0) this.down(callBack);
var m = parseInt((this.time / 1000 / 60) % 60, 10);
var s = parseInt((this.time / 1000) % 60, 10);
var ms = parseInt(this.time % 1000, 10);
el.innerText = this.check(m) + ":" + this.check(s);
}, 1);
}
};
// es7
setTimeout(() => {
initGame();
}, 300);
async function initGame() {
await (() => {
return new Promise((resolve, reject) => {
mask.classList.add("mask-in");
setTimeout(() => {
resolve();
}, 300);
});
})();
// 创建画布
const app = new PIXI.Application(
table.x * unit_size.width,
table.y * unit_size.height,
{
backgroundColor: 0xf1c16c,
antialias: true,
view: document.querySelector("#game"),
resolution: window.devicePixelRatio
}
);
app.stage.interactive = true;
app.stage.addChild(container);
// 绘制表格
creatTable(table, unit_data, async function() {
await (() => {
return new Promise((resolve, reject) => {
setTimeout(() => {
mask.innerText = "初始化完成,祝您玩的开心";
resolve();
}, 1000);
});
})();
await setTimeout(() => {
mask.classList.add("mask-out");
console.log("初始化完成");
}, 500);
});
}
function creatTable(table, block, callBack) {
let row = table.x;
let col = table.y;
let index = 0;
let width = unit_size.width * table.x * unit_size.width;
let height = unit_size.width * table.y * unit_size.height;
let i = 0;
while (i < row) {
unit.push([]);
let k = 0;
while (k < col) {
let sprite = new PIXI.Sprite(unitTexture);
let unitContainer = new PIXI.Container();
let x = (k * unit_size.width) % width;
let y = (i * unit_size.height) % height;
sprite.interactive = true;
sprite.isDown = false;
sprite.isEmpty = true;
sprite.anchor.set(0, 0);
sprite.width = unit_size.width;
sprite.height = unit_size.height;
sprite.index = index++; // 唯一id
unitContainer.width = unit_size.width;
unitContainer.height = unit_size.height;
unitContainer.x = x;
unitContainer.y = y;
sprite.x = 0;
sprite.y = 0;
sprite.X = x / unit_size.width; // x坐标
sprite.Y = y / unit_size.height; // y 坐标
unit[i].push(sprite);
unitContainer.addChild(sprite);
container.addChild(unitContainer);
k++;
}
i++;
}
// 添加事件
for (let row = 0, uLen = unit.length; row < uLen; row++) {
add: for (let col = 0, len = unit[row].length; col < len; col++) {
// console.log(unit[row][col].x / unit_size.width, unit[row][col].y / unit_size.height);
for (let k = 0, fLen = block[row].length; k < fLen; k++) {
if (block[row][k].x === col && block[row][k].y === row) {
unit[row][col].isEmpty = false;
unit[row][col].texture = unitBlockTexture;
unit[row][col].buttonMode = true;
unit[row][col].on("tap", onClick);
unit_block.push(unit[row][col]);
continue add;
}
}
}
}
// 绘制边框
drawBorder(table, unit_data, container);
typeof callBack === "function" ? callBack() : void 0;
}
function onClick() {
input.focus();
const X = this.X,
Y = this.Y;
let cols = [],
rows = unit[Y];
// 重置默认
reset(unit_block);
this.isDown = !this.isDown;
unit.forEach(row => {
if (row[X].X === X) cols.push(row[X]);
});
if (this.selectRow !== undefined) {
if (this.isDown && this.selectRow) {
renderCol(X, Y, cols, rows);
} else {
renderRow(X, Y, rows, cols);
}
} else {
if (this.isDown) {
renderRow(X, Y, rows, cols);
} else {
renderCol(X, Y, cols, rows);
}
}
let key = selectUnit.unit.reduce((total, unit) => {
return total + unit.index.toString();
}, "");
tipsEl.innerText = tips[key];
console.log(key);
}
// 渲染行
function renderRow(x, y, row, col) {
let unit = aroundUnit(row, x, y, true);
let len = unit.length;
// 如果临近元素为为空 (1 代表自身) 渲染列
if (len <= 1) {
renderCol(x, y, col, row);
return;
}
unit.forEach(item => {
item.texture = unitBlockActiveTexture;
item.selectRow = true;
});
let storUnit = stor(unit);
selectUnit.isRow = true;
selectUnit.unit = storUnit;
}
// 渲染列
function renderCol(x, y, col, row) {
let unit = aroundUnit(col, x, y, false);
let len = unit.length;
// 如果临近元素为空 (1 代表自身) 渲染行
if (len <= 1) {
renderRow(x, y, row, col, true);
return;
}
unit.forEach(item => {
item.texture = unitBlockActiveTexture;
item.selectRow = false;
});
let storUnit = stor(unit);
selectUnit.isRow = false;
selectUnit.unit = storUnit;
}
// 获取相邻元素 arry 一维数组 行 或者 列
function aroundUnit(arry, x, y, selectRow) {
let n = selectRow ? x : y;
let len = arry.length;
let unit = [];
for (let i = 0; i < len; i++) {
if (arry[n + i] && arry[n + i].isEmpty) break;
if (arry[n + i]) {
unit.push(arry[n + i]);
}
}
for (let i = 1; i < len; i++) {
if (arry[n - i] && arry[n - i].isEmpty) break;
if (arry[n - i]) unit.push(arry[n - i]);
}
return unit;
}
// 重置视图
function reset(unit) {
unit.forEach(item => {
item.isDown = false;
item.texture = unitBlockTexture;
});
}
// 排序
function stor(unit) {
function compare(property) {
return function(a, b) {
var value1 = a[property];
var value2 = b[property];
return value1 - value2;
};
}
return unit.sort(compare("index"));
}
function drawBorder(table, block, ctx) {
let row = table.x,
col = table.y;
let containerWidth = row * unit_size.width,
containerHeight = col * unit_size.height;
let startY = unit_size.height,
startX = unit_size.width;
drawLine(
{ width: 1, color: 0xb38743, alpha: 1 },
ctx,
containerWidth,
containerHeight,
startX,
startY,
row,
col
);
}
// 绘制线条
function drawLine(lineStyle, ctx, width, height, X, Y, col, row) {
let i = 1,
n = 1;
const graphics = new PIXI.Graphics();
// 绘制行
while (i < row) {
graphics.beginFill();
graphics.lineStyle(lineStyle.width, lineStyle.color, lineStyle.alpha);
graphics.moveTo(0, Y * i);
graphics.lineTo(width, Y * i);
graphics.endFill();
ctx.addChild(graphics);
i++;
}
// 绘制列
while (n < col) {
graphics.beginFill();
graphics.lineStyle(lineStyle.width, lineStyle.color, lineStyle.alpha);
graphics.moveTo(X * n, 0);
graphics.lineTo(X * n, height);
graphics.endFill();
ctx.addChild(graphics);
n++;
}
}
// 解决键盘遮住 输入框
$(document).on("focus", "input", function(e) {
setTimeout(
() => {
// this.scrollIntoViewIfNeeded(false);
this.scrollIntoView({
block: "end",
behavior: "smooth"
});
},
device.ios ? 200 : 0
);
});
// 输入
$(".input-container").on("click", ".game-input-send", async function(e) {
let value = $.trim(input.value);
if (value.length > selectUnit.unit.length) {
modal.toast(`最大可输入字数为${selectUnit.unit.length}个`);
return false;
}
let param = selectUnit.unit.map((item, index) => {
return {
value: value.charAt(index),
x: item.X,
y: item.Y,
index: item.index
};
});
console.log(param);
let flag = await new Promise((resolve, reject) => {
$.ajax({
async: true,
type: "post",
url: "./game.php",
data: { data: param },
success: function(data) {
if (data.status === "0") {
modal.toast("填错了哦");
input.value = "";
resolve(false);
} else {
resolve(true);
}
},
error: function(err) {
console.log(err);
}
});
});
if (!flag) return;
[...value].forEach((chart, index) => {
if (selectUnit.unit[index].parent.children.length > 1) {
selectUnit.unit[index].parent.children.forEach((sprite, i) => {
if (sprite.type === "text") sprite.text = chart;
});
return false;
}
let richText = new PIXI.Text(chart, fontStyle);
richText.type = "text";
richText.anchor.set(0.5);
richText.x = selectUnit.unit[index].parent.width / 2;
richText.y = selectUnit.unit[index].parent.height / 2;
selectUnit.unit[index].parent.addChild(richText);
// container.addChild(richText);
});
input.value = "";
});
});
PHP
后端就比较容易了,前端传每一个字格 id 以及值,后端验证字格 id 值 就行了
/*
* @Author: Edward
* @Date: 2018-05-02 23:27:31
* @Last Modified by: Edward
* @Email: 809537981@qq.com
* @Last Modified time: 2018-05-03 16:26:08
*/
header('Content-Type:application/json; charset=utf-8');
$post = $_POST['data'];
$data = [
'1' => '局',
'11' => '外',
'5' => '繁',
'6' => '星',
'7' => '春',
'8' => '水',
'13' => '极',
'33' => '主',
'18' => '浒',
'21' => '人',
'22' => '类',
'23' => '简',
'24' => '史',
'28' => '传',
'27' => '人',
'26' => '名',
'37' => '民',
'46' => '梦',
'47' => '的',
'57' => '名',
'48' => '解',
'49' => '析',
'64' => '三',
'65' => '国',
'66' => '演',
'67' => '义',
'67' => '义',
'74' => '毛',
'81' => '呼',
'82' => '兰',
'83' => '河',
'84' => '传',
'40' => '存',
'41' => '在',
'42' => '主',
'43' => '义',
'51' => '细',
'61' => '雨',
'71' => '中',
'81' => '呼',
'90' => '呐',
'91' => '喊',
];
$flag = false;
foreach($post as $index => $arry) {
if ($data[$arry['index']] == $arry['value']) {
$flag = true;
} else {
$flag = false;
}
}
if ($flag) {
echo '{"status": "1"}';
} else {
echo '{"status": "0"}';
}
收工