【Canvas】使用Vue3+TS+Canvas實現(xiàn)五子棋

1.效果預(yù)覽

動畫.gif

?? 項目地址?

2.實現(xiàn)思路

  • 創(chuàng)建畫布
<canvas id="chess_canvas" ref="chess_canvas"></canvas>
  • 創(chuàng)建繪制對象和棋盤DOM
let ctx: CanvasRenderingContext2D;

const chess_canvas = ref<HTMLCanvasElement>();
  • 使用二維數(shù)組記錄棋盤格的信息
let record: number[][] = [];
  • 記錄當(dāng)前要下的棋子是黑還是白
let isBlack = true;
  • 初始化畫布妥曲,將畫布的大小設(shè)置為600x600业舍,邊緣留出20妓柜,棋盤規(guī)模為14x14,每個格子的大小為40
  ctx = chess_canvas.value?.getContext("2d") as CanvasRenderingContext2D;
  chess_canvas.value!.width = 600;
  chess_canvas.value!.height = 600;
  chess_canvas.value!.style.background = "#e3cdb0";
  isBlack = true;
  • 繪制棋盤的橫線和豎線
  for (let i = 0; i < 15; i++) {
    ctx.beginPath();
    ctx.moveTo(20, 20 + 40 * i);
    ctx.lineTo(580, 20 + 40 * i);
    ctx.stroke();
    ctx.closePath();
  }

  for (let i = 0; i < 15; i++) {
    ctx.beginPath();
    ctx.moveTo(20 + 40 * i, 20);
    ctx.lineTo(20 + 40 * i, 580);
    ctx.stroke();
    ctx.closePath();
  }
  • 初始化棋盤格數(shù)組客燕,0代表未下,1代表黑子内边,2代表白子
  for (let i = 0; i < 15; i++) {
    record[i] = new Array(15).fill(0);
  }
  • 給棋盤添加點擊事件票灰,棋子只能落到格子的交叉點上,所以要對x的位置和y的位置進行取整契吉,選取離點擊點位置最近的格子跳仿,繪制棋子,之后更換棋子顏色
  chess_canvas.value!.onclick = e => {
    let x = e.offsetX - (e.offsetX % 40) + 20;
    let y = e.offsetY - (e.offsetY % 40) + 20;

    let pX = (x - 20) / 40;
    let pY = (y - 20) / 40;
    // 棋盤的X和Y和二維數(shù)組的X和Y是相反的
    if (record[pY][pX] != 0) {
      return;
    }

    ctx.beginPath();
    ctx.arc(x, y, 10, 0, 2 * Math.PI);
    ctx.fillStyle = isBlack ? "black" : "white";
    ctx.fill();
    ctx.closePath();

    record[pY][pX] = isBlack ? 1 : 2;
    isBlack = !isBlack;
  };
  • 在下過一個棋子之后捐晶,判斷這顆棋子是否能和棋盤中的其他棋子連成5顆菲语。
    我的做法是這樣的(自己想的,代碼量不多惑灵,有不足之處請指教):每下過一顆棋子山上,以該棋子為中心,讀取【上4顆+該棋子+下4顆】英支,【左4顆+該棋子+右4顆】佩憾,【左上對角4顆+該棋子+左下對角4顆】,【右上對角4顆+該棋子+右下對角4顆】潭辈,存儲為4個數(shù)組:


    image.png

    然后依次判斷這4個數(shù)組中鸯屿,是否有連續(xù)5個相同的數(shù)字(數(shù)字的值等于剛剛下過的棋子的值)澈吨,判斷算法是這樣設(shè)計的:一個數(shù)組最大長度為9,若該數(shù)組中含有5個相同的數(shù)字寄摆,那么該數(shù)組的中間數(shù)字必定為這5個數(shù)字中的一個谅辣,然后以該中間數(shù)為中心,若左邊的數(shù)和中間數(shù)相等婶恼,則一直向左尋找邊界桑阶,若右邊的數(shù)和中間的數(shù)相等,則一直向右尋找邊界勾邦,如果找到左右邊界蚣录,判斷左右邊界的差值,如果差值為4眷篇,那么已經(jīng)連成了5子萎河。

const isWin = (x: number, y: number) => {
  let res: number[][] = [];
  let xadd = [];
  let yadd = [];
  let zadd = [];
  let wadd = [];
  let flag = false;
  // 上 下 左 右 +4
  for (let i = 4; i >= -4; i--) {
    if (x - i >= 0 && x - i <= 14) {
      xadd.push(record[x - i][y]);
    }
    if (y - i >= 0 && y - i <= 14) {
      yadd.push(record[x][y - i]);
    }
    if (x - i >= 0 && x - i <= 14 && y - i >= 0 && y - i <= 14) {
      zadd.push(record[x - i][y - i]);
    }
    if (x + i >= 0 && x + i <= 14 && y - i >= 0 && y - i <= 14) {
      wadd.push(record[x + i][y - i]);
    }
  }
  res.push(xadd);
  res.push(yadd);
  res.push(zadd);
  res.push(wadd);

  let target = record[x][y];
  res.forEach(arr => {
    let mid = Math.floor(arr.length / 2);
    let left = mid;
    let right = mid;
    while (arr[left] == target) {
      left--;
    }
    while (arr[right] == target) {
      right++;
    }
    if (right - 1 - (left + 1) == 4) {
      flag = true;
    }
  });
  return flag;
};
  • 連成5子后,顯示勝利彈窗蕉饼,可以選擇重玩
    setTimeout(() => {
      if (isWin(pY, pX)) {
        const con = confirm(`${!isBlack ? "黑棋" : "白棋"}贏了虐杯!是否重新開局?`);
        ctx.clearRect(0, 0, 600, 600);
        con && initCanvas();
      }
    }, 10);

3.全部代碼

  <div class="box">
    <canvas id="chess_canvas" ref="chess_canvas"></canvas>
  </div>
import { onBeforeUnmount, onMounted, ref } from "vue";
/** 繪制對象 */
let ctx: CanvasRenderingContext2D;

/** 棋盤DOM */
const chess_canvas = ref<HTMLCanvasElement>();

/** 記錄棋盤格信息的數(shù)組 */
let record: number[][] = [];

/** 當(dāng)前是否要下黑棋 */
let isBlack = true;

/** 初始化 */
const initCanvas = () => {
  ctx = chess_canvas.value?.getContext("2d") as CanvasRenderingContext2D;
  chess_canvas.value!.width = 600;
  chess_canvas.value!.height = 600;
  chess_canvas.value!.style.background = "#e3cdb0";

  isBlack = true;

  drawCheckerboard();
};

/** 繪制棋盤:每個棋格大小40*40 */
const drawCheckerboard = () => {
  for (let i = 0; i < 15; i++) {
    ctx.beginPath();
    ctx.moveTo(20, 20 + 40 * i);
    ctx.lineTo(580, 20 + 40 * i);
    ctx.stroke();
    ctx.closePath();
  }

  for (let i = 0; i < 15; i++) {
    ctx.beginPath();
    ctx.moveTo(20 + 40 * i, 20);
    ctx.lineTo(20 + 40 * i, 580);
    ctx.stroke();
    ctx.closePath();
  }

  /** 初始化棋盤格數(shù)組昧港,0代表未下擎椰,1代表黑子,2代表白子 */
  for (let i = 0; i < 15; i++) {
    record[i] = new Array(15).fill(0);
  }

  /** 給棋盤添加點擊事件 */
  chess_canvas.value!.onclick = e => {
    // 進行取整创肥,確保棋子落在最近的棋盤格交叉點上
    let x = e.offsetX - (e.offsetX % 40) + 20;
    let y = e.offsetY - (e.offsetY % 40) + 20;

    let pX = (x - 20) / 40;
    let pY = (y - 20) / 40;

    if (record[pY][pX] != 0) {
      return;
    }

    ctx.beginPath();
    ctx.arc(x, y, 10, 0, 2 * Math.PI);
    ctx.fillStyle = isBlack ? "black" : "white";
    ctx.fill();
    ctx.closePath();

    record[pY][pX] = isBlack ? 1 : 2;
    isBlack = !isBlack;

    setTimeout(() => {
      if (isWin(pY, pX)) {
        const con = confirm(`${!isBlack ? "黑棋" : "白棋"}贏了达舒!是否重新開局?`);
        ctx.clearRect(0, 0, 600, 600);
        con && initCanvas();
      }
    }, 10);
  };
};

const isWin = (x: number, y: number) => {
  let res: number[][] = [];
  let xadd = [];
  let yadd = [];
  let zadd = [];
  let wadd = [];
  let flag = false;
  // 上 下 左 右 +4
  for (let i = 4; i >= -4; i--) {
    if (x - i >= 0 && x - i <= 14) {
      xadd.push(record[x - i][y]);
    }
    if (y - i >= 0 && y - i <= 14) {
      yadd.push(record[x][y - i]);
    }
    if (x - i >= 0 && x - i <= 14 && y - i >= 0 && y - i <= 14) {
      zadd.push(record[x - i][y - i]);
    }
    if (x + i >= 0 && x + i <= 14 && y - i >= 0 && y - i <= 14) {
      wadd.push(record[x + i][y - i]);
    }
  }
  res.push(xadd);
  res.push(yadd);
  res.push(zadd);
  res.push(wadd);

  let target = record[x][y];
  res.forEach(arr => {
    let mid = Math.floor(arr.length / 2);
    let left = mid;
    let right = mid;
    while (arr[left] == target) {
      left--;
    }
    while (arr[right] == target) {
      right++;
    }
    if (right - 1 - (left + 1) == 4) {
      flag = true;
    }
  });
  return flag;
};

onMounted(() => {
  initCanvas();
});
onBeforeUnmount(() => {
  ctx.clearRect(0, 0, 600, 600);
});
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末叹侄,一起剝皮案震驚了整個濱河市巩搏,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌圈膏,老刑警劉巖塔猾,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異稽坤,居然都是意外死亡丈甸,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進店門尿褪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來睦擂,“玉大人,你說我怎么就攤上這事杖玲《俪穑” “怎么了?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長臼闻。 經(jīng)常有香客問我鸿吆,道長,這世上最難降的妖魔是什么述呐? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任惩淳,我火速辦了婚禮,結(jié)果婚禮上乓搬,老公的妹妹穿的比我還像新娘思犁。我一直安慰自己,他們只是感情好进肯,可當(dāng)我...
    茶點故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布激蹲。 她就那樣靜靜地躺著,像睡著了一般江掩。 火紅的嫁衣襯著肌膚如雪学辱。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天频敛,我揣著相機與錄音项郊,去河邊找鬼。 笑死斟赚,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的差油。 我是一名探鬼主播拗军,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蓄喇!你這毒婦竟也來了发侵?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤妆偏,失蹤者是張志新(化名)和其女友劉穎刃鳄,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體钱骂,經(jīng)...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡叔锐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了见秽。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片愉烙。...
    茶點故事閱讀 39,932評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖解取,靈堂內(nèi)的尸體忽然破棺而出步责,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布蔓肯,位于F島的核電站遂鹊,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏蔗包。R本人自食惡果不足惜稿辙,卻給世界環(huán)境...
    茶點故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望气忠。 院中可真熱鬧邻储,春花似錦、人聲如沸旧噪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽淘钟。三九已至宦赠,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間米母,已是汗流浹背勾扭。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留铁瞒,地道東北人妙色。 一個月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像慧耍,于是被迫代替她去往敵國和親身辨。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,884評論 2 354

推薦閱讀更多精彩內(nèi)容