Heim > Artikel > Web-Frontend > HTML5 Canvas implementiert Code-Sharing für das Backgammon-Spiel (Bilder und Text)
Einführung in den Hintergrund
Da ich GDI+ in Winform in C# und im Java-Grafikpaket verwendet habe, um Backgammon zu erstellen, bin ich mit dieser logischen Idee vertraut. Kürzlich möchte ich jedoch die Canvas-Zeichenfunktion von HTML5 überprüfen (Unternehmen verwenden es im Allgemeinen nicht), daher habe ich mich natürlich nicht auf den vorherigen Client-Code und den Computer-KI-Algorithmus (Network Borrowing) bezogen Natürlich gibt es auf Baidu viele Backgammons, die in HTML5 erstellt wurden, aber es ist immer gut, eine Seite zu erkennen. Okay, lasst uns aufhören zu reden und zur Sache kommen. ^_^
Einführung in Schnittstellenfunktionen und Funktionen, die in Zukunft hinzugefügt werden können
Aktuelle Schnittstellenfunktionen:
Hauptschnittstelle enthält
1: Kampfoptionen zwischen Mensch, Mensch und Mensch-Maschine 2: Auswahl des Erscheinungsbilds einer Schachfigur 3: Auswahl des Schachbretthintergrunds 4: Auswahl der Schachbrettlinienfarbe
Die Spieloberfläche umfasst
1: Spielername 2: Spielerfigur 3: Wer spielt gerade Schach und Hintergrundpositionierung 4: Spielerpunktzahl 5: Funktionsmenübereich (Neustart und unendliches Bedauern im Schach) 6 : Brettbereich 7. Schachfiguren nach Sieg verbinden 8. Die letzte Schachposition blinkt und wird angezeigt 9. Cursorpositionierung
Spielende-Schnittstelle
1: Hintergrundbild „Sieg“ 2 : Siegreicher Spielername 3: Weiter zur nächsten Schaltfläche
Funktionen können hinzugefügt werden
1. Zurück zur Hauptoberfläche 2. Speichern Sie die Spiel und zugehörige Daten 3. Lesen Sie das Spiel und zugehörige Daten 4. Tauschen Sie Charaktere aus 5. Online-Kampf (2 Maschinen) 6. Aufzeichnung der gesamten Bedenkzeit beider Parteien
Beurteilung des Screenshots der Benutzeroberfläche (habe mir nicht die Zeit genommen verschönern, es ist hässlich)
Gesamtdesign und Einführung in den Hauptcode
Gesamtdesign
Schachspielprozess: Spieler- oder Computer-KI spielt Schach---> > Legen Sie die zweidimensionalen Koordinatenwerte von Schachfiguren fest ----> Logik (logisches Urteil) - ---> >
↑ |
| . ↓
< ;------------------- ------------------------------ -------------------- -------------------------Keine fünf Söhne
Reueprozess (jeder spielt gegen jeden): Ein Spieler bereut den Zug----> Löschen Sie das letzte Schachfigurenbild ---> Löschen Sie den zweidimensionalen Koordinatenwert der Schachfigur ---> Neupositionierung, um die letzte Schachposition anzuzeigen und zu blinken
Reueprozess (Mensch-Maschine-Kampf): Spieler bereut Schach----> Aufgezeichnete Bilder ---> Löschen Sie die zweidimensionalen Koordinatenwerte der beiden Schachfiguren ---> Positionieren Sie die letzte Schachposition neu und zeigen Sie sie an
Einführung in den Hauptcode
Der Hauptcode ist in zwei Blöcke unterteilt: 1. Schnittstellenlogikblock 2. Spielhauptblock (Schnittstelle und Spielcode sind getrennt). , mit klarer Logik und Arbeitsteilung Klar)
Schnittstellenlogikcode
<script type="text/javascript"> var gb = null; var infoboj = document.getElementsByClassName("info")[0]; var pl1obj = document.getElementById("pl1"); var pl2obj = document.getElementById("pl2"); var plname1obj = document.getElementById("plname1"); var plname2obj = document.getElementById("plname2"); var chesstypeobj = document.getElementsByName("chesstype"); var chesscolorobj = document.getElementsByName("chesscolor"); var chessbgObj = document.getElementsByName("chessbg"); var winerpnl = document.getElementById("winer"); document.getElementById("startgame").addEventListener("click", function() { function initParams() { var chessTypeValue = 1; if (chesstypeobj.length > 0) { for (var i = 0; i < chesstypeobj.length; i++) { if (chesstypeobj[i].checked) { chessTypeValue = chesstypeobj[i].value; break; } } } var linevalue = ""; if (chesscolorobj.length > 0) { for (var i = 0; i < chesscolorobj.length; i++) { if (chesscolorobj[i].checked) { linevalue = chesscolorobj[i].value; break; } } } var bcorimgvalue = ""; if (chessbgObj.length > 0) { for (var i = 0; i < chessbgObj.length; i++) { if (chessbgObj[i].checked) { bcorimgvalue = chessbgObj[i].value; break; } } } return { lineColor: linevalue, chessType: chessTypeValue, //1 色彩棋子 2 仿真棋子 playAName: plname1Input.value, playBName: plname2Input.value, backColorORImg: bcorimgvalue, playAImg: "http://sandbox.runjs.cn/uploads/rs/62/nbqodq5i/playA.png", playBImg: "http://sandbox.runjs.cn/uploads/rs/62/nbqodq5i/playB.png", playerBIsComputer:openComputer.checked }; } document.getElementById("cc").style.display = "block"; gb = new gobang(initParams()); /** * 设置一些界面信息 * @param {Object} opt */ gb.info = function(opt) { infoboj.style.visibility = "visible"; document.getElementsByClassName("startpnl")[0].style.visibility = "hidden"; plname1obj.innerHTML = opt.playAName; plname2obj.innerHTML = opt.playBName; if (opt.chessType == 1) { var span1 = document.createElement("span"); pl1obj.insertBefore(span1, plname1obj); var span2 = document.createElement("span"); pl2obj.insertBefore(span2, plname2obj); } else { var img1 = document.createElement("img"); img1.src = opt.playAImg; pl1obj.insertBefore(img1, plname1obj); var img2 = document.createElement("img"); img2.src = opt.playBImg; pl2obj.insertBefore(img2, plname2obj); } } /** * 每次下棋后触发事件 * @param {Object} c2d */ gb.operate = function(opt, c2d) { if (!c2d.winer || c2d.winer <= 0) { pl1obj.removeAttribute("class", "curr"); pl2obj.removeAttribute("class", "curr"); if (c2d.player == 1) { pl2obj.setAttribute("class", "curr"); } else { pl1obj.setAttribute("class", "curr"); } document.getElementById("backChessman").innerHTML="悔棋("+c2d.canBackTimes+")"; } else { var winname = c2d.winer == 1 ? opt.playAName : opt.playBName; var str = "恭喜,【" + winname + "】赢了!" alert(str); winerpnl.style.display = "block"; document.getElementById("winerName").innerHTML = "恭喜,【" + winname + "】赢了!"; document.getElementById("pl" + c2d.winer).style.backgroundColor = "pink"; document.getElementById("scoreA").innerHTML = c2d.playScoreA; document.getElementById("scoreB").innerHTML = c2d.playScoreB; } } gb.start(); }); document.getElementById("openComputer").addEventListener("change", function() { if (this.checked) { plname2Input.value = "电脑"; plname2Input.disabled = "disabled"; } else { plname2Input.value = "玩家二"; plname2Input.disabled = ""; } }); //document.getElementById("openComputer").checked="checked"; //重新开始 function restartgui() { if (gb) { winerpnl.style.display = "none"; pl1obj.removeAttribute("class", "curr"); pl2obj.removeAttribute("class", "curr"); document.getElementById("pl1").style.backgroundColor = ""; document.getElementById("pl2").style.backgroundColor = ""; gb.restart(); } }; </script>
Spielkörpercodeblock ( Enthält nur FunktionenDeklarationscode)
// ========== // =name:gobang 游戏 // =anthor:jasnature // =last modify date:2016-04-13 // ========== (function(win) { var gb = function(option) { var self = this, canObj = document.getElementById("cc"), can = canObj.getContext("2d"); self.contextObj = canObj; self.context = can; if (!self.context) { alert("浏览器不支持html5"); return; }; self.Opt = { lineColor: "green", chessType: 1, //1 色彩棋子 2 仿真棋子 playAName: "play1", playBName: "play2", playAColor: "red", playBColor: "blue", playAImg: "img/playA.png", playBImg: "img/playB.png", backColorORImg: "default", playerBIsComputer: false }; self.operate; //合并属性 for (var a in option) { //console.log(opt[a]); self.Opt[a] = option[a]; }; //私有变量 var my = {}; my.enableCalcWeightNum = false; //显示AI分数 my.gameover = false; //棋盘相关 my.baseWidth = 30; my.lastFocusPoint = {}; //鼠标最后移动到的坐标点,计算后的 my.cw = self.contextObj.offsetWidth; //棋盘宽 my.ch = self.contextObj.offsetHeight; //高 my.xlen = Math.ceil(my.cw / my.baseWidth); //行数 my.ylen = Math.ceil(my.ch / my.baseWidth); //列 my.chessRadius = 14; //棋子半径 my.playerBIsComputer = false; //棋手B是否是电脑 my.ComputerThinking = false; //电脑是否在下棋 my.goBackC2dIsComputer = false; //最后下棋是否为电脑 my.switcher = 1; //由谁下棋了 1-a 2-b or computer my.winer = -1; //赢家,值参考my.switcher my.playScoreA = 0; my.playScoreB = 0; //x,y 正方形数量(20*20) my.rectNum = my.xlen; //存储已下的点 my.rectMap = []; my.NO_CHESS = -1; //没有棋子标识 my.goBackC2d = {}; //最后下的数组转换坐标 my.downChessmanStackC2d = []; // 记录已下棋子的顺序和位置,堆栈 my.focusFlashInterval = null; //焦点闪烁线程 my.focusChangeColors = ["red", "fuchsia", "#ADFF2F", "yellow", "purple", "blue"]; my.eventBinded = false; my.currChessBackImg = null; my.currChessAImg = null; my.currChessBImg = null; my.currDrawChessImg = null; my.ChessDownNum = 0; //2个玩家 下棋总数 /** * 开始游戏 */ self.start = function() { }; /** * 重新开始游戏 */ self.restart = function() { }; /** * 悔棋一步 ,清棋子,并返回上一次参数 */ self.back = function() { } /** * 初始化一些数据 */ function init() { } // self.paint = function() { // // //window.requestAnimationFrame(drawChessboard); // }; /** * 游戏逻辑 */ function logic(loc, iscomputer) { }; /** * 判断是否有玩家胜出 * @param {Object} c2d */ function isWin(c2d) { return false; } /** * 连接赢家棋子线 * @param {Object} points */ function joinWinLine(points) { } /** * 画棋盘 */ function drawChessboard() { }; /** * 画棋子 * @param {Object} loc 鼠标点击位置 */ function drawChessman(c2d) { } function drawRect(lastRecord, defColor) { } /** * 闪烁最后下棋点 */ function flashFocusChessman() { } /** * 清棋子 * @param {Object} c2d */ function clearChessman() { } /** * @param {Object} loc * @return {Object} I 二维数组横点(),J二维数组纵点,IX 横点起始坐标,JY纵点起始坐标,player 最后下棋玩, winer 赢家 */ function calc2dPoint(loc) { var txp = Math.floor(loc.x / my.baseWidth), typ = Math.floor(loc.y / my.baseWidth) dxp = txp * my.baseWidth, dyp = typ * my.baseWidth; loc.I = txp; loc.J = typ; loc.IX = dxp; loc.JY = dyp; return loc; } my.isChangeDraw = true; /** * 位置移动光标 * @param {Object} loc */ function moveFocus(loc) { } /** * 绑定事件 */ function bindEvent() { if (!my.eventBinded) { self.contextObj.addEventListener("touchstart", function(event) { //console.log(event); var touchObj = event.touches[0]; eventHandle({ s: "touch", x: touchObj.clientX - this.offsetLeft, y: touchObj.clientY - this.offsetTop }) }); self.contextObj.addEventListener("click", function(event) { //console.log("click event"); eventHandle({ s: "click", x: event.offsetX, y: event.offsetY }) }); self.contextObj.addEventListener("mousemove", function(event) { //console.log("mousemove event"); moveFocus({ x: event.offsetX, y: event.offsetY }); }); my.eventBinded = true; } function eventHandle(ps) { if (!my.gameover && !my.ComputerThinking) { logic(ps); if (my.playerBIsComputer && my.switcher == 2) { my.ComputerThinking = true; var pp = AI.analysis(my.goBackC2d.I, my.goBackC2d.J); logic({ I: pp.x, J: pp.y }, true); my.ComputerThinking = false; } } event.preventDefault(); event.stopPropagation(); return false; } } }; win.gobang = gb; })(window);Hauptalgorithmus Einführung
玩家OR电脑胜出算法
/** * 判断是否有玩家胜出 * @param {Object} c2d */ function isWin(c2d) { //四个放心计数 竖 横 左斜 右斜 var hcount = 0, vcount = 0, lbhcount = 0, rbhcount = 0, temp = 0; var countArray = []; //左-1 for (var i = c2d.I; i >= 0; i--) { temp = my.rectMap[i][c2d.J]; if (temp < 0 || temp !== c2d.player) { break; } hcount++; countArray.push({ I: i, J: c2d.J }); } //右-1 for (var i = c2d.I + 1; i < my.rectMap.length; i++) { temp = my.rectMap[i][c2d.J]; if (temp < 0 || temp !== c2d.player) { break; } hcount++; countArray.push({ I: i, J: c2d.J }); } if (countArray.length < 5) { countArray = []; //上-2 for (var j = c2d.J; j >= 0; j--) { temp = my.rectMap[c2d.I][j]; if (temp < 0 || temp !== c2d.player) { break; } vcount++; countArray.push({ I: c2d.I, J: j }); } //下-2 for (var j = c2d.J + 1; j < my.rectMap[c2d.I].length; j++) { temp = my.rectMap[c2d.I][j]; if (temp < 0 || temp !== c2d.player) { break; } vcount++; countArray.push({ I: c2d.I, J: j }); } } if (countArray.length < 5) { countArray = []; //左上 for (var i = c2d.I, j = c2d.J; i >= 0, j >= 0; i--, j--) { if (i < 0 || j < 0) break; temp = my.rectMap[i][j]; if (temp < 0 || temp !== c2d.player) { break; } lbhcount++; countArray.push({ I: i, J: j }); } //右下 if (c2d.I < my.rectMap.length - 1 && c2d.I < my.rectMap[0].length - 1) { for (var i = c2d.I + 1, j = c2d.J + 1; i < my.rectMap.length, j < my.rectMap[0].length; i++, j++) { if (i >= my.rectMap.length || j >= my.rectMap.length) break; temp = my.rectMap[i][j]; if (temp < 0 || temp !== c2d.player) { break; } lbhcount++; countArray.push({ I: i, J: j }); } } } if (countArray.length < 5) { countArray = []; //右上 for (var i = c2d.I, j = c2d.J; i < my.rectMap.length, j >= 0; i++, j--) { if (i >= my.rectMap.length || j < 0) break; temp = my.rectMap[i][j]; if (temp < 0 || temp !== c2d.player) { break; } rbhcount++; countArray.push({ I: i, J: j }); } //左下 if (c2d.I >= 1 && c2d.J < my.rectMap[0].length - 1) { for (var i = c2d.I - 1, j = c2d.J + 1; i > 0, j < my.rectMap[0].length; i--, j++) { if (j >= my.rectMap.length || i < 0) break; temp = my.rectMap[i][j]; if (temp < 0 || temp !== c2d.player) { break; } rbhcount++; countArray.push({ I: i, J: j }); } } } if (hcount >= 5 || vcount >= 5 || lbhcount >= 5 || rbhcount >= 5) { my.winer = c2d.player; my.gameover = true; joinWinLine(countArray); return true; } return false; }
算法简介:主要思路是搜索最后落下棋子的位置(二维坐标)计算 米 字形线坐标,看是否有连续5个或以上棋子出现。
连接赢家棋子线
/** * 连接赢家棋子线 * @param {Object} points */ function joinWinLine(points) { points.sort(function(left, right) { return (left.I + left.J) > (right.I + right.J); }); var startP = points.shift(); var endP = points.pop(); var poffset = my.baseWidth / 2; can.strokeStyle = "#FF0000"; can.lineWidth = 2; can.beginPath(); var spx = startP.I * my.baseWidth + poffset, spy = startP.J * my.baseWidth + poffset; can.arc(spx, spy, my.baseWidth / 4, 0, 2 * Math.PI, false); can.moveTo(spx, spy); var epx = endP.I * my.baseWidth + poffset, epy = endP.J * my.baseWidth + poffset; can.lineTo(epx, epy); can.moveTo(epx + my.baseWidth / 4, epy); can.arc(epx, epy, my.baseWidth / 4, 0, 2 * Math.PI, false); can.closePath(); can.stroke(); }
算法简介:根据赢家返回的连子位置集合,做坐标大小位置排序,直接使用lineto 连接 第一个棋子和最后一个
坐标换算
/** * 坐标换算 * @param {Object} loc * @return {Object} I 二维数组横点(),J二维数组纵点,IX 横点起始坐标,JY纵点起始坐标,player 最后下棋玩, winer 赢家 */ function calc2dPoint(loc) { var txp = Math.floor(loc.x / my.baseWidth), typ = Math.floor(loc.y / my.baseWidth) dxp = txp * my.baseWidth, dyp = typ * my.baseWidth; loc.I = txp; loc.J = typ; loc.IX = dxp; loc.JY = dyp; return loc; }
算法简介:这个比较简单,根据每个格子的宽度计算出实际坐标
电脑AI主要代码(修改来源于网络)
/** * AI棋型分析 */ AI.analysis = function(x, y) { //如果为第一步则,在玩家棋周围一格随机下棋,保证每一局棋第一步都不一样 if (my.ChessDownNum == 1) { return this.getFirstPoint(x, y); } var maxX = 0, maxY = 0, maxWeight = 0, i, j, tem; for (i = BOARD_SIZE - 1; i >= 0; i--) { for (j = BOARD_SIZE - 1; j >= 0; j--) { if (my.rectMap[i][j] !== -1) { continue; } tem = this.computerWeight(i, j, 2); if (tem > maxWeight) { maxWeight = tem; maxX = i; maxY = j; } if (my.enableCalcWeightNum) { can.clearRect(i * 30 + 2, j * 30 + 2, 24, 24); can.fillText(maxWeight, i * 30 + 5, j * 30 + 15, 30); } } } return new Point(maxX, maxY); }; //下子到i,j X方向 结果: 多少连子 两边是否截断 AI.putDirectX = function(i, j, chessColor) { var m, n, nums = 1, side1 = false, //两边是否被截断 side2 = false; for (m = j - 1; m >= 0; m--) { if (my.rectMap[i][m] === chessColor) { nums++; } else { if (my.rectMap[i][m] === my.NO_CHESS) { side1 = true; //如果为空子,则没有截断 } break; } } for (m = j + 1; m < BOARD_SIZE; m++) { if (my.rectMap[i][m] === chessColor) { nums++; } else { if (my.rectMap[i][m] === my.NO_CHESS) { side2 = true; } break; } } return { "nums": nums, "side1": side1, "side2": side2 }; }; //下子到i,j Y方向 结果 AI.putDirectY = function(i, j, chessColor) { var m, n, nums = 1, side1 = false, side2 = false; for (m = i - 1; m >= 0; m--) { if (my.rectMap[m][j] === chessColor) { nums++; } else { if (my.rectMap[m][j] === my.NO_CHESS) { side1 = true; } break; } } for (m = i + 1; m < BOARD_SIZE; m++) { if (my.rectMap[m][j] === chessColor) { nums++; } else { if (my.rectMap[m][j] === my.NO_CHESS) { side2 = true; } break; } } return { "nums": nums, "side1": side1, "side2": side2 }; }; //下子到i,j XY方向 结果 AI.putDirectXY = function(i, j, chessColor) { var m, n, nums = 1, side1 = false, side2 = false; for (m = i - 1, n = j - 1; m >= 0 && n >= 0; m--, n--) { if (my.rectMap[m][n] === chessColor) { nums++; } else { if (my.rectMap[m][n] === my.NO_CHESS) { side1 = true; } break; } } for (m = i + 1, n = j + 1; m < BOARD_SIZE && n < BOARD_SIZE; m++, n++) { if (my.rectMap[m][n] === chessColor) { nums++; } else { if (my.rectMap[m][n] === my.NO_CHESS) { side2 = true; } break; } } return { "nums": nums, "side1": side1, "side2": side2 }; }; AI.putDirectYX = function(i, j, chessColor) { var m, n, nums = 1, side1 = false, side2 = false; for (m = i - 1, n = j + 1; m >= 0 && n < BOARD_SIZE; m--, n++) { if (my.rectMap[m][n] === chessColor) { nums++; } else { if (my.rectMap[m][n] === my.NO_CHESS) { side1 = true; } break; } } for (m = i + 1, n = j - 1; m < BOARD_SIZE && n >= 0; m++, n--) { if (my.rectMap[m][n] === chessColor) { nums++; } else { if (my.rectMap[m][n] === my.NO_CHESS) { side2 = true; } break; } } return { "nums": nums, "side1": side1, "side2": side2 }; }; /** * 计算AI下棋权重 * chessColor 玩家1为玩家2为AI */ AI.computerWeight = function(i, j, chessColor) { //基于棋盘位置权重(越靠近棋盘中心权重越大) var weight = 19 - (Math.abs(i - 19 / 2) + Math.abs(j - 19 / 2)), pointInfo = {}; //某点下子后连子信息 //x方向 pointInfo = this.putDirectX(i, j, chessColor); weight += this.weightStatus(pointInfo.nums, pointInfo.side1, pointInfo.side2, true); //AI下子权重 pointInfo = this.putDirectX(i, j, chessColor - 1); weight += this.weightStatus(pointInfo.nums, pointInfo.side1, pointInfo.side2, false); //player下子权重 //y方向 pointInfo = this.putDirectY(i, j, chessColor); weight += this.weightStatus(pointInfo.nums, pointInfo.side1, pointInfo.side2, true); //AI下子权重 pointInfo = this.putDirectY(i, j, chessColor - 1); weight += this.weightStatus(pointInfo.nums, pointInfo.side1, pointInfo.side2, false); //player下子权重 //左斜方向 pointInfo = this.putDirectXY(i, j, chessColor); weight += this.weightStatus(pointInfo.nums, pointInfo.side1, pointInfo.side2, true); //AI下子权重 pointInfo = this.putDirectXY(i, j, chessColor - 1); weight += this.weightStatus(pointInfo.nums, pointInfo.side1, pointInfo.side2, false); //player下子权重 //右斜方向 pointInfo = this.putDirectYX(i, j, chessColor); weight += this.weightStatus(pointInfo.nums, pointInfo.side1, pointInfo.side2, true); //AI下子权重 pointInfo = this.putDirectYX(i, j, chessColor - 1); weight += this.weightStatus(pointInfo.nums, pointInfo.side1, pointInfo.side2, false); //player下子权重 return weight; }; //权重方案 活:两边为空可下子,死:一边为空 //其实还有很多种方案,这种是最简单的 AI.weightStatus = function(nums, side1, side2, isAI) { var weight = 0; switch (nums) { case 1: if (side1 && side2) { weight = isAI ? 15 : 10; //一 } break; case 2: if (side1 && side2) { weight = isAI ? 100 : 50; //活二 } else if (side1 || side2) { weight = isAI ? 10 : 5; //死二 } break; case 3: if (side1 && side2) { weight = isAI ? 500 : 200; //活三 } else if (side1 || side2) { weight = isAI ? 30 : 20; //死三 } break; case 4: if (side1 && side2) { weight = isAI ? 5000 : 2000; //活四 } else if (side1 || side2) { weight = isAI ? 400 : 100; //死四 } break; case 5: weight = isAI ? 100000 : 10000; //五 break; default: weight = isAI ? 500000 : 250000; break; } return weight; };
AI分析:这个只是最简单的算法,其实很简单,计算每个没有下棋坐标的分数,也是按照 米 字形 计算,计算格子8个方向出现的 一个棋子 二个棋子 三个棋子 四个棋子,其中还分为是否被截断,其实就是边缘是否被堵死。
其实这个AI算法后续还有很多可以优化,比如 断跳 二活 其实就是2个交叉的 活二 , 因为是断掉的所以没有纳入算法权重计算,如果加入这个算法,估计很难下赢电脑了。
如符号图:
* *
* *
空位
下这里
因为不是连续的,所有没有纳入。
Das obige ist der detaillierte Inhalt vonHTML5 Canvas implementiert Code-Sharing für das Backgammon-Spiel (Bilder und Text). Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!