react-konva-demo
簡介
在線例子
以create-react-app作為腳手架,通過react+redux+react-konva 寫一個簡單的五子棋游戲,分享一下react-konva的實際使用例子
(沒有人機對戰功能喲)
react-konva是一個react組件化的canvas庫,來繪制五子棋
redux處理游戲中的數據流
思路介紹
把五子棋游戲分為兩個部分,一個是設定部分,另外一部分就是游戲本身。
將五子棋游戲本身分解為三層,即分解為三個組件
底層是棋盤,包括棋盤顏色與網格的繪制
中間層是繪制棋子在棋盤上的狀態的
最頂層是操作層負責落子以及繪制光標狀態。
五子棋狀態儲存在一個二維數組中,通過判斷值是否大于0判斷是否已經落子
棋盤繪制
// src/components/Board.js
import React,{Component} from 'react';
import {Shape, Group, Rect} from 'react-konva';
export default class Board extends Component {
constructor(args){
super(...args)
//綁定上下文
this._grid = this._grid.bind(this);
}
//繪制網格的函數
_grid(context){
/*fill the board */
context.beginPath();
context.fillStyle = "#976B42"
context.fill();
context.closePath();
let col= 0, row= 0,
{ width,height, gridSize } = this.props;
col= Math.ceil(width/gridSize);
row= Math.ceil(height/gridSize);
//繪制列
for(let i= 0; i<= col; i++) {
context.beginPath();
context.moveTo(gridSize*i,0);
context.lineTo(gridSize*i,height);
context.stroke();
context.closePath();
}
//繪制行
for(let j= 0; j<= row; j++){
context.beginPath();
context.moveTo(0,gridSize*j);
context.lineTo(width,gridSize*j);
context.stroke();
context.closePath();
}
}
render(){
let { width,height} = this.props;
console.log(this.props)
return (
<Group>
<Rect
fill = {"#fed386"}
width= {width}
height= {height}
strokeWidth= {2}
stroke={ 'black'}
/>
<Shape
x= {0}
y= {0}
width= {width}
height= {height}
strokeWidth= {2}
sceneFunc = {this._grid}
stroke={ 'black'}
/>
</Group>
)
}
}
Rect是konva中繪制矩形的控件,
Shape是繪制圖形的控件即繪制自定義控件通過sceneFunc函數傳入context進行自定義繪制
棋子繪制
import React ,{Component} from 'react';
import {Shape} from 'react-konva';
export default class ChessDisplay extends Component {
constructor(props){
super(props)
this._drawChess = this._drawChess.bind(this);
}
_drawChess(context){
const {chessStore,gridSize,cheeSize} = this.props;
if(!chessStore.hasOwnProperty('length')){
throw Error("must be a valid array");
}
//需要初始化數組,
//type 1 白棋 2 黑棋
for(let i = 0; i < chessStore.length; i++){
for(let j = 0; j < chessStore[i].length; j++){
if(chessStore[i][j] >= 1){
if(chessStore[i][j] === 1){
context.fillStyle = "white";
}
if(chessStore[i][j] === 2)
{
context.fillStyle = "black";
}
context.beginPath();
/*context.arc(x,y,r,sAngle,eAngle,counterclockwise);*/
context.arc(i*gridSize,j*gridSize,cheeSize,0,2*Math.PI);
context.fill();
context.closePath();
}
}
}
}
render(){
let {width, height} = this.props;
return (
<Shape
x= {0}
y= {0}
width= {width}
height= {height}
strokeWidth= {2}
sceneFunc = {this._drawChess}
stroke={ 'black'}
/>
)
}
}
落子組件
import React, {Component} from 'react';
import {Shape, Rect, Circle, Group} from 'react-konva';
import PropTypes from 'prop-types'
export default class HandleChess extends Component {
static propTypes=({
chess:PropTypes.array,
addChess:PropTypes.func,
endChess:PropTypes.func,
turn:PropTypes.number
})
constructor(props){
super(props)
this.__handleChess = this._handleChess.bind(this);
this._pointer = this._pointer.bind(this);
this.state = {
pointerX:null,
pointerY:null
}
}
_handleChess(){
let stage = this.props.getStage().getStage(),mousePos,x,y,pointX,pointY,chess,winner,
{chessStore, addChess, endChess, turn, isBegin, setWinner} = this.props;
if(!isBegin){
return
}
const gridSize = 50 ;
mousePos= stage.getPointerPosition();
x = mousePos.x,
y = mousePos.y;
pointX = mousePos.x > gridSize ? mousePos.x/gridSize :1 ;
pointY = mousePos.y > gridSize ? mousePos.y/gridSize :1;
pointX = Math.floor(mousePos.x % gridSize >= 25 ? ++pointX : pointX);
pointY = Math.floor(mousePos.y % gridSize >= 25 ? ++pointY : pointY);
/*add Chess*/
if(chessStore[pointX]&&!chessStore[pointX][pointY]){
chess = {
x:pointX,
y:pointY,
type:turn,
}
if(pointX > 0 && pointY > 0){
addChess(chess)
}
try{
if(this._isWin(chessStore,chess)){
if(chess.type === 1 ){
winner = "白棋"
setWinner(1)
}
if(chess.type ===2 ){
winner = "黑棋"
setWinner(2)
}
alert(`${winner}勝利,游戲結束`)
endChess();
}
}catch(e){
console.log(e)
}
}
}
//掃描游戲是否已經結束了
_isWin(store,chess){
let pointX = chess.x,
pointY = chess.y,
type = chess.type,
count = 0 ;
//掃描行
for(let i = pointX - 5; i <= pointX + 5; i++){
if(i<=0) {
continue;
}
if(type === store[i][pointY]){
count++;
}
if(count === 5){
return 1
}
if(i + 1 == store.length){
break;
}
}
//清空count
count = 0 ;
//掃描列
for(let j = pointY - 5; j<= pointY + 5; j++){
if(j<=0) {
continue;
}else{
if(type === store[pointX][j]){
count++;
}
}
if(count === 5){
return 1
}
if(j + 1 == store[pointX].length){
break;
}
}
//清空count
count = 0 ;
//掃描右對稱軸
for(let i = pointX - 5, j = pointY - 5;i <= pointX + 5; i++,j++){
if(i <= 0 || j<= 0) {
continue;
}else{
if(type === store[i][j]){
count++;
}
}
if(count === 5){
return 1
}
if(i + 1 == store.length || j + 1 == store[i].length){
break;
}
}
//清空count
count = 0 ;
//掃描左對稱軸
for(let i = pointX + 5, j = pointY +5; i>=pointX - 5; i--,j--){
if(i + 1 >= store.length || j + 1 >= store[i].length) {
continue;
}else{
if(type === store[i][j]){
count++;
}
}
if(count === 5){
return 1
}
if(i <= 0 || j<= 0){
break;
}
}
return 0;
}
/*繪制一個跟隨光標的棋子*/
_pointer(){
let stage = this.props.getStage().getStage(),
mousePos = stage.getPointerPosition();
this.setState({
pointerX: mousePos.x,
pointerY: mousePos.y
})
}
/*父組件需要傳入stage?*/
render(){
let winWidth= 800, winHeight= 800;
const {turn, cheeSize} = this.props;
let {pointerX, pointerY} = this.state;
return (
<Group>
{this.props.isBegin && pointerX && pointerY ?
<Circle
x = {pointerX}
y = {pointerY}
fill = {turn===1? "white" : "black"}
radius = {20}
/>
:null
}
<Rect
x = {0}
y = {0}
height = {winHeight}
width = {winWidth}
onClick = {this.__handleChess}
onMousemove = {this._pointer}
/>
</Group>
);
}
}