改下哥尼斯堡網頁程式

分享和讨论Javascript相关的话题
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5043
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 174 次
被圈友点赞: 204 次
联系:

改下哥尼斯堡網頁程式

帖子 ejsoon »

konigsberg7771.7z
(7.12 KiB) 已下载 4 次
哥尼斯堡是個好玩的桌遊,以及網頁遊戲。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5043
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 174 次
被圈友点赞: 204 次
联系:

Re: 改下哥尼斯堡網頁程式

帖子 ejsoon »

找到上載程式中的移動動畫部分,把它改為用js遞迴來控制動畫移動。

gemini fast:

代码: 全选

在您提供的手機遊戲代碼中,原本的移動動畫是使用 `setInterval` 配合 `setTimeout` 來處理路徑移動與結束動作的。

要將其改為**遞迴(Recursive)**結構,我們可以利用一個遞迴函數來處理路徑中每一段的移動,並在所有路徑點(`route_tmp`)走完後執行回調(Callback)。

以下是修改後的代碼部分。請將原有的 `createAnimateMotion` 函數替換為以下內容:

### 修改後的代碼

```javascript
// 使用遞迴控制的移動動畫
function createAnimateMotion() {
    let moveman = men[current_man];
    moveman.remove();
    toparea.appendChild(moveman);
    moveman.style.setProperty('transform', 'translate(0px)');
    moveman.classList.add("man");

    // 當前座標累加器
    let currentX = 0;
    let currentY = 0;

    /**
     * 遞迴移動函數
     * @param {number} step - 當前移動到第幾段路徑
     */
    function performStep(step) {
        // 遞迴終止條件:如果已經走完所有路徑段
        if (step >= route_tmp.length - 1) {
            // 延遲一段時間後執行橋樑翻轉和結果判定,確保最後一幀動畫完成
            cssturntimeout = setTimeout(function () {
                let last_dot = dots[route_tmp[route_tmp.length - 1]];
                moveman.innerHTML = "";
                moveman.setAttribute("cx", last_dot.getAttribute("cx"));
                moveman.setAttribute("cy", last_dot.getAttribute("cy"));
                moveman.classList.remove("man");
                moveman.style.setProperty('transform', 'translate(0px)');
                turnBridge();
            }, 1000); // 與 CSS transition 1s 同步
            return;
        }

        // 計算當前點到下一點的位移
        let startDot = dots[route_tmp[step]];
        let endDot = dots[route_tmp[step + 1]];
        
        let dx = parseInt(endDot.getAttribute("cx")) - parseInt(startDot.getAttribute("cx"));
        let dy = parseInt(endDot.getAttribute("cy")) - parseInt(startDot.getAttribute("cy"));

        currentX += dx;
        currentY += dy;

        // 執行 CSS 動畫移動
        moveman.style.setProperty('transform', 'translate(' + currentX + 'px, ' + currentY + 'px)');

        // 遞迴調用:1秒後移動下一步
        cssmoveinroute = setTimeout(function() {
            performStep(step + 1);
        }, 1000);
    }

    // 啟動遞迴(從第0步開始)
    setTimeout(function() {
        performStep(0);
    }, 7);
}
```

### 修改重點說明:

1.  **邏輯變更**:將原本散落在 `setInterval`(負責中間路徑)和 `setTimeout`(負責最後一段和清理)的邏輯統一整合進 `performStep` 遞迴函數中。
2.  **遞迴終止**:當 `step` 達到 `route_tmp.length - 1` 時,代表移動結束,此時觸發原本在 `cssturntimeout` 中的清理邏輯(更新 `cx/cy` 並調用 `turnBridge`)。
3.  **座標處理**:使用 `currentX` 和 `currentY` 閉包變量來累積位移量,這比在每次遞迴中重新解析 DOM 屬性更穩定。
4.  **計時器替換**:原本全域變量 `cssmoveinroute` 現在存儲的是 `setTimeout` 的 ID,這與您程式碼中其他地方調用 `clearInterval(cssmoveinroute)` 可能會產生輕微不相容。如果需要完全嚴謹,請將程式中所有的 `clearInterval(cssmoveinroute)` 改為 `clearTimeout(cssmoveinroute)`。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5043
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 174 次
被圈友点赞: 204 次
联系:

Re: 改下哥尼斯堡網頁程式

帖子 ejsoon »

改進要求:之前會把兩個men加到不同的toparea和bottomarea,現在改進為兩個men同時都加到toparea和bottomarea,其中bottomarea中的兩個men始終顯示,toparea的men只有在移動時顯示,移動結束則隱藏。當移動時,相同的men在不同的area都一起移動。不會再有remove和appendChild(men)。


只需給出所要修改的地方,所有的代碼和注釋都要使用英文。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5043
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 174 次
被圈友点赞: 204 次
联系:

Re: 改下哥尼斯堡網頁程式

帖子 ejsoon »

gemini pro:

代码: 全选

Here are the modifications required to fulfill your request. We will introduce a new array `men_top` to track the duplicate men in the `toparea`, append them permanently at initialization, and synchronize their movements and attributes with the `men` (which will stay in `bottomarea`). The `remove()` and `appendChild()` calls during gameplay will be eliminated entirely.

### 1. Add `men_top` to Global Variables
Find the `const men = [];` line and add `men_top` right below it.

```javascript
const dots = [];
const men = [];
const men_top = []; // Add this line to keep track of the men in toparea
```

### 2. Update `addDots()`
Find the condition `if (i == 3 || i == 6)` inside `addDots()` and replace the `konigsberg.appendChild(man);` logic.

**Replace this part:**
```javascript
		man.onclick = manOnClick;
		men.push(man);
		konigsberg.appendChild(man);
	}
}
```

**With this:**
```javascript
		man.onclick = manOnClick;
		men.push(man);
		bottomarea.appendChild(man); // Add bottom men to bottomarea permanently

		let man_top = man.cloneNode(true);
		man_top.onclick = manOnClick;
		men_top.push(man_top);
		toparea.appendChild(man_top); // Add top men to toparea permanently
	}
}
```

### 3. Update `displayGame()`
Remove the dynamic `remove()` and `appendChild()` methods, and instead control the visibility of `men_top`.

**Replace this part:**
```javascript
		men[0].setAttribute("stroke", "none");
		men[1].setAttribute("stroke", "none");
		men[1].remove();
		bottomarea.appendChild(men[1]);
	}
	else if (game_status == 1 || game_status == 3) {
		men[0].classList.remove("hide");
		men[1].classList.remove("hide");
		men[current_man].setAttribute("stroke", "none");
		men[~current_man & 1].setAttribute("stroke", "none");
		men[current_man].remove();
		men[~current_man & 1].remove();
		toparea.appendChild(men[current_man]);
		bottomarea.appendChild(men[~current_man & 1]);
		// disdplay
```

**With this:**
```javascript
		men[0].setAttribute("stroke", "none");
		men[1].setAttribute("stroke", "none");
		men_top[0].classList.add("hide"); // Ensure top men are hidden
		men_top[1].classList.add("hide");
	}
	else if (game_status == 1 || game_status == 3) {
		men[0].classList.remove("hide");
		men[1].classList.remove("hide");
		men[current_man].setAttribute("stroke", "none");
		men[~current_man & 1].setAttribute("stroke", "none");
		men_top[0].classList.add("hide"); // Ensure top men are hidden
		men_top[1].classList.add("hide");
		// disdplay
```

### 4. Update `btn_play.onclick`
Sync the reset properties for both `men` and `men_top`.

**Replace this part:**
```javascript
		men[0].setAttribute("i", 3)
		men[0].setAttribute("cx", dots[3].getAttribute("cx"))
		men[0].setAttribute("cy", dots[3].getAttribute("cy"))
		men[0].setAttribute("stroke", "none")
		men[1].setAttribute("i", 6)
		men[1].setAttribute("cx", dots[6].getAttribute("cx"))
		men[1].setAttribute("cy", dots[6].getAttribute("cy"))
		men[1].setAttribute("stroke", "none")
```

**With this:**
```javascript
		men[0].setAttribute("i", 3);
		men[0].setAttribute("cx", dots[3].getAttribute("cx"));
		men[0].setAttribute("cy", dots[3].getAttribute("cy"));
		men[0].setAttribute("stroke", "none");
		men_top[0].setAttribute("i", 3);
		men_top[0].setAttribute("cx", dots[3].getAttribute("cx"));
		men_top[0].setAttribute("cy", dots[3].getAttribute("cy"));
		men_top[0].setAttribute("stroke", "none");

		men[1].setAttribute("i", 6);
		men[1].setAttribute("cx", dots[6].getAttribute("cx"));
		men[1].setAttribute("cy", dots[6].getAttribute("cy"));
		men[1].setAttribute("stroke", "none");
		men_top[1].setAttribute("i", 6);
		men_top[1].setAttribute("cx", dots[6].getAttribute("cx"));
		men_top[1].setAttribute("cy", dots[6].getAttribute("cy"));
		men_top[1].setAttribute("stroke", "none");
```

### 5. Update `btn_finish.onclick`
Sync the destination node `i` configuration.

**Replace this part:**
```javascript
	// man sit
	men[current_man].setAttribute("i", route_tmp[route_tmp.length - 1]);
	createAnimateMotion();
```

**With this:**
```javascript
	// man sit
	men[current_man].setAttribute("i", route_tmp[route_tmp.length - 1]);
	men_top[current_man].setAttribute("i", route_tmp[route_tmp.length - 1]);
	createAnimateMotion();
```

### 6. Update `historyCommen()`
Remove the DOM manipulations, synchronize the history states to `men_top`, and hide the top elements.

**Replace this part:**
```javascript
	men[0].classList.remove("hide");
	men[0].setAttribute("i", dots[current_board[0]].getAttribute("i"));
	men[0].setAttribute("cx", dots[current_board[0]].getAttribute("cx"));
	men[0].setAttribute("cy", dots[current_board[0]].getAttribute("cy"));
	men[0].setAttribute("stroke", "none");
	men[1].classList.remove("hide");
	men[1].setAttribute("i", dots[current_board[1]].getAttribute("i"));
	men[1].setAttribute("cx", dots[current_board[1]].getAttribute("cx"));
	men[1].setAttribute("cy", dots[current_board[1]].getAttribute("cy"));
	men[1].setAttribute("stroke", "none");
	men[current_man].remove();
	men[~current_man & 1].remove();
	// animation motion reset
	men[current_man].style.setProperty('transform', 'translate(0px)');
	men[~current_man & 1].style.setProperty('transform', 'translate(0px)');
	clearInterval(cssmoveinroute);
	clearTimeout(cssturntimeout);
	toparea.appendChild(men[current_man]);
	bottomarea.appendChild(men[~current_man & 1]);
	men[0].innerHTML = men[1].innerHTML = "";
```

**With this:**
```javascript
	men[0].classList.remove("hide");
	men[0].setAttribute("i", dots[current_board[0]].getAttribute("i"));
	men[0].setAttribute("cx", dots[current_board[0]].getAttribute("cx"));
	men[0].setAttribute("cy", dots[current_board[0]].getAttribute("cy"));
	men[0].setAttribute("stroke", "none");

	men_top[0].setAttribute("i", dots[current_board[0]].getAttribute("i"));
	men_top[0].setAttribute("cx", dots[current_board[0]].getAttribute("cx"));
	men_top[0].setAttribute("cy", dots[current_board[0]].getAttribute("cy"));
	men_top[0].setAttribute("stroke", "none");

	men[1].classList.remove("hide");
	men[1].setAttribute("i", dots[current_board[1]].getAttribute("i"));
	men[1].setAttribute("cx", dots[current_board[1]].getAttribute("cx"));
	men[1].setAttribute("cy", dots[current_board[1]].getAttribute("cy"));
	men[1].setAttribute("stroke", "none");

	men_top[1].setAttribute("i", dots[current_board[1]].getAttribute("i"));
	men_top[1].setAttribute("cx", dots[current_board[1]].getAttribute("cx"));
	men_top[1].setAttribute("cy", dots[current_board[1]].getAttribute("cy"));
	men_top[1].setAttribute("stroke", "none");

	// animation motion reset
	men[0].style.setProperty('transform', 'translate(0px)');
	men[1].style.setProperty('transform', 'translate(0px)');
	men_top[0].style.setProperty('transform', 'translate(0px)');
	men_top[1].style.setProperty('transform', 'translate(0px)');
	
	men_top[0].classList.add("hide");
	men_top[1].classList.add("hide");

	clearInterval(cssmoveinroute);
	clearTimeout(cssturntimeout);
	
	men[0].innerHTML = men[1].innerHTML = "";
	men_top[0].innerHTML = men_top[1].innerHTML = "";
```

### 7. Update `createAnimateMotion()`
Modify the animation handler to animate both `moveman` and `moveman_top` concurrently. Also, handle `moveman_top`'s visibility (show during animation, hide when done).

**Replace the entire `createAnimateMotion()` function:**
```javascript
// create animate motion
function createAnimateMotion () {
	let moveman = men[current_man];
	let moveman_top = men_top[current_man];
	
	// Show the top man during animation
	moveman_top.classList.remove("hide");

	moveman.style.setProperty('transform', 'translate(0px)');
	moveman_top.style.setProperty('transform', 'translate(0px)');
	moveman.classList.add("man");
	moveman_top.classList.add("man");
	
	setTimeout(function () {
		let dur = route_tmp.length - 1;
		if (route_tmp.length > 1) {
			var rgo = 0;
			var rgoxbo = dots[route_tmp[rgo]].getAttribute("cx");
			var rgoxao = dots[route_tmp[rgo + 1]].getAttribute("cx");
			var rgoxo = parseInt(rgoxao) - parseInt(rgoxbo);
			var rgoybo = dots[route_tmp[rgo]].getAttribute("cy");
			var rgoyao = dots[route_tmp[rgo + 1]].getAttribute("cy");
			var rgoyo = parseInt(rgoyao) - parseInt(rgoybo);
			
			let translateVal = 'translate(' + rgoxo + 'px, ' + rgoyo + 'px)';
			moveman.style.setProperty('transform', translateVal);
			moveman_top.style.setProperty('transform', translateVal);
		}
		if (route_tmp.length > 2) {
			rgo++;
			cssmoveinroute = setInterval(function () {
				let rgoxb = dots[route_tmp[rgo]].getAttribute("cx");
				let rgoxa = dots[route_tmp[rgo + 1]].getAttribute("cx");
				rgoxo = rgoxo + parseInt(rgoxa) - parseInt(rgoxb);
				let rgoyb = dots[route_tmp[rgo]].getAttribute("cy");
				let rgoya = dots[route_tmp[rgo + 1]].getAttribute("cy");
				rgoyo = rgoyo + parseInt(rgoya) - parseInt(rgoyb);
				
				let translateVal = 'translate(' + rgoxo + 'px, ' + rgoyo + 'px)';
				moveman.style.setProperty('transform', translateVal);
				moveman_top.style.setProperty('transform', translateVal);
				
				rgo++;
				if (rgo >= route_tmp.length - 1) {
					clearInterval(cssmoveinroute);
				}
			}, 1000);
		}
		cssturntimeout = setTimeout(function () {
			let last_dot = dots[route_tmp[route_tmp.length - 1]];
			moveman.innerHTML = "";
			moveman.setAttribute("cx", last_dot.getAttribute("cx"));
			moveman.setAttribute("cy", last_dot.getAttribute("cy"));
			moveman.classList.remove("man");
			moveman.style.setProperty('transform', 'translate(0px)');
			
			moveman_top.innerHTML = "";
			moveman_top.setAttribute("cx", last_dot.getAttribute("cx"));
			moveman_top.setAttribute("cy", last_dot.getAttribute("cy"));
			moveman_top.classList.remove("man");
			moveman_top.style.setProperty('transform', 'translate(0px)');
			moveman_top.classList.add("hide"); // Hide top man after movement
			
			turnBridge();
		}, dur * 1000 + 7);
	}, 7);
}
```
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5043
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 174 次
被圈友点赞: 204 次
联系:

Re: 改下哥尼斯堡網頁程式

帖子 ejsoon »

改進要求:

之前會把兩個men加到不同的toparea和bottomarea,現在改進為兩個men同時都加到toparea和bottomarea。其中bottomarea中的兩個men始終顯示,並綁定manOnClick。toparea的men不綁定manOnClick,一般時候隱藏,在manOnClick觸發之後顯示,在移動完並且橋轉完之後隱藏。

當toparea的men顯示時,對應的bottomarea的men隱藏。反之當toparea的men隱藏時,對應的bottomarea的men顯示。當移動時,相同的men在不同的area都一起移動。不會再有remove和appendChild(men)。

只需給出所要候改的地方,所有的代碼和注釋都要使用英文。代碼不要有空行。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5043
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 174 次
被圈友点赞: 204 次
联系:

Re: 改下哥尼斯堡網頁程式

帖子 ejsoon »

gemini pro:

代码: 全选

Here are the parts of the code that need to be changed based on your requirements. All code and comments are provided in English, and there are no blank lines.

**1. Update array variables**
Find:
```javascript
const dots = [];
const men = [];
const arrows = [];
```
Replace with:
```javascript
const dots = [];
const men_top = [];
const men_bottom = [];
const arrows = [];
```

**2. Update `addDots` function to append to both areas**
Find:
```javascript
		appendarea.appendChild(dots[i]);
		if (i == 3 || i == 6) {
			let man = dot.cloneNode(true);
			let manfill = i == 3 ? "#33d" : "#3d3";
			man.setAttribute("fill", manfill);
			man.setAttribute("stroke-width", "4");
			man.setAttribute("stroke-dasharray", "7 7");
			man.setAttribute("stroke", "lightyellow");
			man.setAttribute("i", i);
			man.classList.add("hide");
			man.classList.add("man");
			man.onclick = manOnClick;
			men.push(man);
			konigsberg.appendChild(man);
		}
	}
}
```
Replace with:
```javascript
		appendarea.appendChild(dots[i]);
		if (i == 3 || i == 6) {
			let man_bottom = dot.cloneNode(true);
			let manfill = i == 3 ? "#33d" : "#3d3";
			man_bottom.setAttribute("fill", manfill);
			man_bottom.setAttribute("stroke-width", "4");
			man_bottom.setAttribute("stroke-dasharray", "7 7");
			man_bottom.setAttribute("stroke", "lightyellow");
			man_bottom.setAttribute("i", i);
			man_bottom.classList.add("hide");
			man_bottom.classList.add("man");
			man_bottom.onclick = manOnClick;
			men_bottom.push(man_bottom);
			bottomarea.appendChild(man_bottom);
			let man_top = man_bottom.cloneNode(true);
			man_top.onclick = null;
			men_top.push(man_top);
			toparea.appendChild(man_top);
		}
	}
}
```

**3. Update `dotOnClick` references**
Find:
```javascript
function dotOnClick () {
	if (game_status == 2 || game_status == 4) {
		let i = parseInt(this.getAttribute("i"));
		let otherman_i = men[~current_man & 1].getAttribute("i");
```
Replace with:
```javascript
function dotOnClick () {
	if (game_status == 2 || game_status == 4) {
		let i = parseInt(this.getAttribute("i"));
		let otherman_i = men_bottom[~current_man & 1].getAttribute("i");
```

**4. Update `manOnClick` visibility toggle**
Find:
```javascript
function manOnClick () {
	if (game_status == 1 || game_status == 3) {
		let clickmandot = parseInt(this.getAttribute("i"));
		let currentmandot = men[current_man].getAttribute("i");
		if (clickmandot == currentmandot) {
			if (judgeResult()) {
				game_status++;
				drawPlanRouteStart(clickmandot);
				route_tmp.push(clickmandot);
				this.appendChild(dashoffset.cloneNode());
				btn_finish.classList.remove("hide");
				this.setAttribute("stroke", "lightyellow");
				// history
			}
```
Replace with:
```javascript
function manOnClick () {
	if (game_status == 1 || game_status == 3) {
		let clickmandot = parseInt(this.getAttribute("i"));
		let currentmandot = men_bottom[current_man].getAttribute("i");
		if (clickmandot == currentmandot) {
			if (judgeResult()) {
				game_status++;
				drawPlanRouteStart(clickmandot);
				route_tmp.push(clickmandot);
				men_top[current_man].appendChild(dashoffset.cloneNode());
				men_top[current_man].classList.remove("hide");
				men_bottom[current_man].classList.add("hide");
				btn_finish.classList.remove("hide");
				men_bottom[current_man].setAttribute("stroke", "lightyellow");
				men_top[current_man].setAttribute("stroke", "lightyellow");
				// history
			}
```

**5. Update `displayGame` logic**
Find:
```javascript
function displayGame () {
	if (game_status == 0) {
		addDots();
		// hide
		men[0].classList.add("hide");
		men[1].classList.add("hide");
		men_blue.classList.add("hide");
		men_green.classList.add("hide");
		btn_cancel.classList.add("hide");
		btn_back.classList.add("hide");
		btn_finish.classList.add("hide");
		btn_history_prev.classList.add("hide");
		btn_history_next.classList.add("hide");
		// display
		btn_play.classList.remove("hide");
		btn_history_set.classList.remove("hide");
		men[0].setAttribute("stroke", "none");
		men[1].setAttribute("stroke", "none");
		men[1].remove();
		bottomarea.appendChild(men[1]);
	}
	else if (game_status == 1 || game_status == 3) {
		men[0].classList.remove("hide");
		men[1].classList.remove("hide");
		men[current_man].setAttribute("stroke", "none");
		men[~current_man & 1].setAttribute("stroke", "none");
		men[current_man].remove();
		men[~current_man & 1].remove();
		toparea.appendChild(men[current_man]);
		bottomarea.appendChild(men[~current_man & 1]);
		// disdplay
		if (game_status == 3) {
			men_blue.classList.add("hide");
			men_green.classList.remove("hide");
		} else {
			men_blue.classList.remove("hide");
			men_green.classList.add("hide");
		}
		btn_play.classList.add("hide");
	}
```
Replace with:
```javascript
function displayGame () {
	if (game_status == 0) {
		addDots();
		// hide
		men_bottom[0].classList.add("hide");
		men_bottom[1].classList.add("hide");
		men_top[0].classList.add("hide");
		men_top[1].classList.add("hide");
		men_blue.classList.add("hide");
		men_green.classList.add("hide");
		btn_cancel.classList.add("hide");
		btn_back.classList.add("hide");
		btn_finish.classList.add("hide");
		btn_history_prev.classList.add("hide");
		btn_history_next.classList.add("hide");
		// display
		btn_play.classList.remove("hide");
		btn_history_set.classList.remove("hide");
		men_bottom[0].setAttribute("stroke", "none");
		men_bottom[1].setAttribute("stroke", "none");
		men_top[0].setAttribute("stroke", "none");
		men_top[1].setAttribute("stroke", "none");
	}
	else if (game_status == 1 || game_status == 3) {
		men_bottom[0].classList.remove("hide");
		men_bottom[1].classList.remove("hide");
		men_top[0].classList.add("hide");
		men_top[1].classList.add("hide");
		men_bottom[current_man].setAttribute("stroke", "none");
		men_bottom[~current_man & 1].setAttribute("stroke", "none");
		men_top[current_man].setAttribute("stroke", "none");
		men_top[~current_man & 1].setAttribute("stroke", "none");
		// disdplay
		if (game_status == 3) {
			men_blue.classList.add("hide");
			men_green.classList.remove("hide");
		} else {
			men_blue.classList.remove("hide");
			men_green.classList.add("hide");
		}
		btn_play.classList.add("hide");
	}
```

**6. Update references in `judgeResult`**
Find:
```javascript
			// compare history
			let men0_i = parseInt(men[0].getAttribute("i"));
			let men1_i = parseInt(men[1].getAttribute("i"));
```
Replace with:
```javascript
			// compare history
			let men0_i = parseInt(men_bottom[0].getAttribute("i"));
			let men1_i = parseInt(men_bottom[1].getAttribute("i"));
```
Find:
```javascript
	else if (game_status == 1 || game_status == 3) {
		let dots_array = [parseInt(men[current_man].getAttribute("i"))];
		let lines_array = [];
		let otherman_i = parseInt(men[~current_man & 1].getAttribute("i"));
```
Replace with:
```javascript
	else if (game_status == 1 || game_status == 3) {
		let dots_array = [parseInt(men_bottom[current_man].getAttribute("i"))];
		let lines_array = [];
		let otherman_i = parseInt(men_bottom[~current_man & 1].getAttribute("i"));
```

**7. Update `btn_play.onclick`**
Find:
```javascript
		men[0].setAttribute("i", 3)
		men[0].setAttribute("cx", dots[3].getAttribute("cx"))
		men[0].setAttribute("cy", dots[3].getAttribute("cy"))
		men[0].setAttribute("stroke", "none")
		men[1].setAttribute("i", 6)
		men[1].setAttribute("cx", dots[6].getAttribute("cx"))
		men[1].setAttribute("cy", dots[6].getAttribute("cy"))
		men[1].setAttribute("stroke", "none")
```
Replace with:
```javascript
		men_bottom[0].setAttribute("i", 3);
		men_bottom[0].setAttribute("cx", dots[3].getAttribute("cx"));
		men_bottom[0].setAttribute("cy", dots[3].getAttribute("cy"));
		men_bottom[0].setAttribute("stroke", "none");
		men_top[0].setAttribute("i", 3);
		men_top[0].setAttribute("cx", dots[3].getAttribute("cx"));
		men_top[0].setAttribute("cy", dots[3].getAttribute("cy"));
		men_top[0].setAttribute("stroke", "none");
		men_bottom[1].setAttribute("i", 6);
		men_bottom[1].setAttribute("cx", dots[6].getAttribute("cx"));
		men_bottom[1].setAttribute("cy", dots[6].getAttribute("cy"));
		men_bottom[1].setAttribute("stroke", "none");
		men_top[1].setAttribute("i", 6);
		men_top[1].setAttribute("cx", dots[6].getAttribute("cx"));
		men_top[1].setAttribute("cy", dots[6].getAttribute("cy"));
		men_top[1].setAttribute("stroke", "none");
```

**8. Update `btn_finish.onclick`**
Find:
```javascript
	route.classList.add("hide");
	// man sit
	men[current_man].setAttribute("i", route_tmp[route_tmp.length - 1]);
	createAnimateMotion();
```
Replace with:
```javascript
	route.classList.add("hide");
	// man sit
	men_bottom[current_man].setAttribute("i", route_tmp[route_tmp.length - 1]);
	men_top[current_man].setAttribute("i", route_tmp[route_tmp.length - 1]);
	createAnimateMotion();
```

**9. Update `btn_back.onclick`**
Find:
```javascript
		// finish button
		let otherman_i = parseInt(men[~current_man & 1].getAttribute("i"));
```
Replace with:
```javascript
		// finish button
		let otherman_i = parseInt(men_bottom[~current_man & 1].getAttribute("i"));
```

**10. Update `historyCommen` logic without appendChild**
Find:
```javascript
	let current_board = JSON.parse(game_history[game_hp * 2]);
	men[0].classList.remove("hide");
	men[0].setAttribute("i", dots[current_board[0]].getAttribute("i"));
	men[0].setAttribute("cx", dots[current_board[0]].getAttribute("cx"));
	men[0].setAttribute("cy", dots[current_board[0]].getAttribute("cy"));
	men[0].setAttribute("stroke", "none");
	men[1].classList.remove("hide");
	men[1].setAttribute("i", dots[current_board[1]].getAttribute("i"));
	men[1].setAttribute("cx", dots[current_board[1]].getAttribute("cx"));
	men[1].setAttribute("cy", dots[current_board[1]].getAttribute("cy"));
	men[1].setAttribute("stroke", "none");
	men[current_man].remove();
	men[~current_man & 1].remove();
	// animation motion reset
	men[current_man].style.setProperty('transform', 'translate(0px)');
	men[~current_man & 1].style.setProperty('transform', 'translate(0px)');
	clearInterval(cssmoveinroute);
	clearTimeout(cssturntimeout);
	toparea.appendChild(men[current_man]);
	bottomarea.appendChild(men[~current_man & 1]);
	men[0].innerHTML = men[1].innerHTML = "";
```
Replace with:
```javascript
	let current_board = JSON.parse(game_history[game_hp * 2]);
	men_bottom[0].classList.remove("hide");
	men_top[0].classList.add("hide");
	men_bottom[0].setAttribute("i", dots[current_board[0]].getAttribute("i"));
	men_bottom[0].setAttribute("cx", dots[current_board[0]].getAttribute("cx"));
	men_bottom[0].setAttribute("cy", dots[current_board[0]].getAttribute("cy"));
	men_bottom[0].setAttribute("stroke", "none");
	men_top[0].setAttribute("i", dots[current_board[0]].getAttribute("i"));
	men_top[0].setAttribute("cx", dots[current_board[0]].getAttribute("cx"));
	men_top[0].setAttribute("cy", dots[current_board[0]].getAttribute("cy"));
	men_top[0].setAttribute("stroke", "none");
	men_bottom[1].classList.remove("hide");
	men_top[1].classList.add("hide");
	men_bottom[1].setAttribute("i", dots[current_board[1]].getAttribute("i"));
	men_bottom[1].setAttribute("cx", dots[current_board[1]].getAttribute("cx"));
	men_bottom[1].setAttribute("cy", dots[current_board[1]].getAttribute("cy"));
	men_bottom[1].setAttribute("stroke", "none");
	men_top[1].setAttribute("i", dots[current_board[1]].getAttribute("i"));
	men_top[1].setAttribute("cx", dots[current_board[1]].getAttribute("cx"));
	men_top[1].setAttribute("cy", dots[current_board[1]].getAttribute("cy"));
	men_top[1].setAttribute("stroke", "none");
	// animation motion reset
	men_bottom[0].style.setProperty('transform', 'translate(0px)');
	men_bottom[1].style.setProperty('transform', 'translate(0px)');
	men_top[0].style.setProperty('transform', 'translate(0px)');
	men_top[1].style.setProperty('transform', 'translate(0px)');
	clearInterval(cssmoveinroute);
	clearTimeout(cssturntimeout);
	men_bottom[0].innerHTML = men_bottom[1].innerHTML = "";
	men_top[0].innerHTML = men_top[1].innerHTML = "";
```

**11. Update `createAnimateMotion` to run simultaneously**
Find:
```javascript
// create animate motion
function createAnimateMotion () {
	let moveman = men[current_man];
	moveman.remove();
	toparea.appendChild(moveman);
	moveman.style.setProperty('transform', 'translate(0px)');
	moveman.classList.add("man");
	setTimeout(function () {
		let dur = route_tmp.length - 1;
		if (route_tmp.length > 1) {
			var rgo = 0;
			var rgoxbo = dots[route_tmp[rgo]].getAttribute("cx");
			var rgoxao = dots[route_tmp[rgo + 1]].getAttribute("cx");
			var rgoxo = parseInt(rgoxao) - parseInt(rgoxbo);
			var rgoybo = dots[route_tmp[rgo]].getAttribute("cy");
			var rgoyao = dots[route_tmp[rgo + 1]].getAttribute("cy");
			var rgoyo = parseInt(rgoyao) - parseInt(rgoybo);
			moveman.style.setProperty('transform',
				'translate(' + rgoxo + 'px, ' + rgoyo + 'px)');
		}
		if (route_tmp.length > 2) {
			rgo++;
			cssmoveinroute = setInterval(function () {
				let rgoxb = dots[route_tmp[rgo]].getAttribute("cx");
				let rgoxa = dots[route_tmp[rgo + 1]].getAttribute("cx");
				rgoxo = rgoxo + parseInt(rgoxa) - parseInt(rgoxb);
				let rgoyb = dots[route_tmp[rgo]].getAttribute("cy");
				let rgoya = dots[route_tmp[rgo + 1]].getAttribute("cy");
				rgoyo = rgoyo + parseInt(rgoya) - parseInt(rgoyb);
				moveman.style.setProperty('transform',
					'translate(' + rgoxo + 'px, ' + rgoyo + 'px)');
				rgo++;
				if (rgo >= route_tmp.length - 1) {
					clearInterval(cssmoveinroute);
				}
			}, 1000);
		}
		cssturntimeout = setTimeout(function () {
			let last_dot = dots[route_tmp[route_tmp.length - 1]];
			moveman.innerHTML = "";
			moveman.setAttribute("cx", last_dot.getAttribute("cx"));
			moveman.setAttribute("cy", last_dot.getAttribute("cy"));
			moveman.classList.remove("man");
			moveman.style.setProperty('transform', 'translate(0px)');
			turnBridge();
		}, dur * 1000 + 7);
	}, 7);
}
```
Replace with:
```javascript
// create animate motion
function createAnimateMotion () {
	let moveman_top = men_top[current_man];
	let moveman_bottom = men_bottom[current_man];
	moveman_top.style.setProperty('transform', 'translate(0px)');
	moveman_bottom.style.setProperty('transform', 'translate(0px)');
	moveman_top.classList.add("man");
	moveman_bottom.classList.add("man");
	setTimeout(function () {
		let dur = route_tmp.length - 1;
		if (route_tmp.length > 1) {
			var rgo = 0;
			var rgoxbo = dots[route_tmp[rgo]].getAttribute("cx");
			var rgoxao = dots[route_tmp[rgo + 1]].getAttribute("cx");
			var rgoxo = parseInt(rgoxao) - parseInt(rgoxbo);
			var rgoybo = dots[route_tmp[rgo]].getAttribute("cy");
			var rgoyao = dots[route_tmp[rgo + 1]].getAttribute("cy");
			var rgoyo = parseInt(rgoyao) - parseInt(rgoybo);
			let transformStr = 'translate(' + rgoxo + 'px, ' + rgoyo + 'px)';
			moveman_top.style.setProperty('transform', transformStr);
			moveman_bottom.style.setProperty('transform', transformStr);
		}
		if (route_tmp.length > 2) {
			rgo++;
			cssmoveinroute = setInterval(function () {
				let rgoxb = dots[route_tmp[rgo]].getAttribute("cx");
				let rgoxa = dots[route_tmp[rgo + 1]].getAttribute("cx");
				rgoxo = rgoxo + parseInt(rgoxa) - parseInt(rgoxb);
				let rgoyb = dots[route_tmp[rgo]].getAttribute("cy");
				let rgoya = dots[route_tmp[rgo + 1]].getAttribute("cy");
				rgoyo = rgoyo + parseInt(rgoya) - parseInt(rgoyb);
				let transformStr = 'translate(' + rgoxo + 'px, ' + rgoyo + 'px)';
				moveman_top.style.setProperty('transform', transformStr);
				moveman_bottom.style.setProperty('transform', transformStr);
				rgo++;
				if (rgo >= route_tmp.length - 1) {
					clearInterval(cssmoveinroute);
				}
			}, 1000);
		}
		cssturntimeout = setTimeout(function () {
			let last_dot = dots[route_tmp[route_tmp.length - 1]];
			moveman_top.innerHTML = "";
			moveman_bottom.innerHTML = "";
			moveman_top.setAttribute("cx", last_dot.getAttribute("cx"));
			moveman_top.setAttribute("cy", last_dot.getAttribute("cy"));
			moveman_bottom.setAttribute("cx", last_dot.getAttribute("cx"));
			moveman_bottom.setAttribute("cy", last_dot.getAttribute("cy"));
			moveman_top.classList.remove("man");
			moveman_bottom.classList.remove("man");
			moveman_top.style.setProperty('transform', 'translate(0px)');
			moveman_bottom.style.setProperty('transform', 'translate(0px)');
			moveman_top.classList.add("hide");
			moveman_bottom.classList.remove("hide");
			turnBridge();
		}, dur * 1000 + 7);
	}, 7);
}
```
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5043
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 174 次
被圈友点赞: 204 次
联系:

Re: 改下哥尼斯堡網頁程式

帖子 ejsoon »

寫個函式,功能為:當點擊btn_finish時,直接得知其在移動結束後的結局,包括會否被對方shooting。把結局輸出到console。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5043
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 174 次
被圈友点赞: 204 次
联系:

Re: 改下哥尼斯堡網頁程式

帖子 ejsoon »

gemini fast:

代码: 全选

要實現這個功能,我們需要模擬 `btn_finish.onclick` 之後會發生的邏輯。根據程式碼,結局主要由 `turnBridge` 呼叫的 `judgeResult` 決定。

你可以將以下函式加入到你的 `<script>` 標籤中。這個函式會根據當前的 `route_tmp`(玩家規劃的路徑)和 `new_arrow`(新蓋的橋)來模擬移動後的狀態,並判定勝負結果。

```javascript
function checkEndingPreview() {
    // 1. 檢查基本移動合法性 (對應 judgeResult 邏輯)
    if (route_tmp.length <= 1) {
        console.log("結局預測:失敗。原因:未進行任何移動。");
        return;
    }

    if (!(new_arrow.length > 0 || arrows.length >= 12)) {
        console.log("結局預測:失敗。原因:未建立新橋樑且橋樑總數未達上限。");
        return;
    }

    // 2. 模擬移動後的棋子位置
    let endNode = route_tmp[route_tmp.length - 1];
    let otherManNode = parseInt(men[~current_man & 1].getAttribute("i"));

    // 3. 模擬橋樑轉向 (翻轉目前路徑經過的所有橋)
    // 我們複製一份當前的 arrow_map 來進行模擬,不影響實際遊戲
    let temp_arrow_map = JSON.parse(JSON.stringify(arrow_map));
    
    // 把新蓋的橋加入模擬地圖中
    for (let i = 0; i * 2 < new_arrow.length; i++) {
        temp_arrow_map[new_arrow[i * 2]].push(new_arrow[i * 2 + 1]);
    }

    // 模擬轉向:路徑中經過的每一段,如果是箭頭橋則翻轉方向
    for (let x = 0; x < route_tmp.length - 1; x++) {
        let u = route_tmp[x];
        let v = route_tmp[x + 1];
        let idx = temp_arrow_map[u].indexOf(v);
        if (idx > -1) {
            temp_arrow_map[u].splice(idx, 1);
            temp_arrow_map[v].push(u);
        }
    }

    // 4. 判定是否 Shooting (射擊)
    // 從移動後的終點 (endNode) 開始,沿著模擬後的箭頭橋尋找所有可達點
    let reachableNodes = [endNode];
    let isShooting = false;

    for (let i = 0; i < reachableNodes.length; i++) {
        let curr = reachableNodes[i];
        let targets = temp_arrow_map[curr];
        for (let target of targets) {
            if (target === otherManNode) {
                isShooting = true;
                break;
            }
            if (reachableNodes.indexOf(target) === -1) {
                reachableNodes.push(target);
            }
        }
        if (isShooting) break;
    }

    // 5. 輸出結果
    let manColor = current_man === 0 ? "藍方" : "綠方";
    if (isShooting) {
        console.log(`結局預測:${manColor} 移動成功!並且成功 Shooting 對手!`);
    } else {
        // 檢查是否會導致重複局面 (和棋 -6)
        let simulatedStack = stack_arrow.concat(new_arrow);
        // 此處簡化處理,僅提示為一般移動
        console.log(`結局預測:${manColor} 移動成功。目前未發現 Shooting 威脅。`);
    }
}

// 綁定到原本的按鈕逻辑中(可選)
// 你可以在 btn_finish.onclick 的開頭呼叫 checkEndingPreview();
```

### 函式邏輯說明:
1.  **基本判定**:首先根據 `judgeResult` 的邏輯,檢查玩家是否沒走路(結局為 `-2`)或沒蓋橋(結局為 `-3`)。
2.  **模擬地圖**:因為移動後橋樑會根據路徑「翻轉」方向(`turnBridge` 邏輯),所以函式會先建立一個暫時的 `temp_arrow_map`。
3.  **Shooting 偵測**:這是此函式的核心。它從移動結束後的坐標出發,沿著所有**翻轉後**的箭頭橋尋找路徑。如果這條路徑能碰到對手的棋子,就會在控制台輸出「成功 Shooting」。
4.  **與原代碼對應**:
    * `-1` 狀態對應 `isShooting = true`。
    * `-5` 狀態對應移動成功但未射擊。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5043
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 174 次
被圈友点赞: 204 次
联系:

Re: 改下哥尼斯堡網頁程式

帖子 ejsoon »

代码: 全选

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg id="konigsberg" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="720" height="480" viewBox="0 0 720 480" style="height: 100%; width: 100%;"><g id="kongnisbersvg">
				<g class="appendarea"><circle cx="180" cy="48" r="24" fill="#fff" stroke="#333" stroke-width="2" i="0"/><circle cx="360" cy="48" r="24" fill="#fff" stroke="#333" stroke-width="2" i="1"/><circle cx="540" cy="48" r="24" fill="#fff" stroke="#333" stroke-width="2" i="2"/><circle cx="90" cy="203.88457268119896" r="24" fill="#fff" stroke="#333" stroke-width="2" i="3"/><circle cx="270" cy="203.88457268119896" r="24" fill="#fff" stroke="#333" stroke-width="2" i="4"/><circle cx="450" cy="203.88457268119896" r="24" fill="#fff" stroke="#333" stroke-width="2" i="5"/><circle cx="630" cy="203.88457268119896" r="24" fill="#fff" stroke="#333" stroke-width="2" i="6"/><circle cx="180" cy="359.7691453623979" r="24" fill="#fff" stroke="#333" stroke-width="2" i="7"/><circle cx="360" cy="359.7691453623979" r="24" fill="#fff" stroke="#333" stroke-width="2" i="8"/><circle cx="540" cy="359.7691453623979" r="24" fill="#fff" stroke="#333" stroke-width="2" i="9"/><path class="arrowbrg" d="M118,203.88457268119896 v -12 h 100 v -12 l 24 24 l -24 24 v -12 h -100 Z" fill="MediumSeaGreen" stroke="none" stroke-width="2" rotatedeg="0" style="transform: rotate(0deg); transform-origin: 180px 203.885px 0px;"/><path class="arrowbrg" d="M253,281.82685902179844 v -12 h 100 v -12 l 24 24 l -24 24 v -12 h -100 Z" fill="MediumSeaGreen" stroke="none" stroke-width="2" rotatedeg="-120" style="transform: rotate(-120deg); transform-origin: 315px 281.827px 0px;"/><path class="arrowbrg" d="M388,359.7691453623979 v -12 h 100 v -12 l 24 24 l -24 24 v -12 h -100 Z" fill="MediumSeaGreen" stroke="none" stroke-width="2" rotatedeg="0" style="transform: rotate(0deg); transform-origin: 450px 359.769px 0px;"/><path class="arrowbrg" d="M523,125.94228634059948 v -12 h 100 v -12 l 24 24 l -24 24 v -12 h -100 Z" fill="MediumSeaGreen" stroke="none" stroke-width="2" rotatedeg="-120" style="transform: rotate(-120deg); transform-origin: 585px 125.942px 0px;"/><path class="arrowbrg" d="M388,48 v -12 h 100 v -12 l 24 24 l -24 24 v -12 h -100 Z" fill="MediumSeaGreen" stroke="none" stroke-width="2" rotatedeg="0" style="transform: rotate(0deg); transform-origin: 450px 48px 0px;"/><path class="arrowbrg" d="M253,125.94228634059948 v -12 h 100 v -12 l 24 24 l -24 24 v -12 h -100 Z" fill="MediumSeaGreen" stroke="none" stroke-width="2" rotatedeg="-60" style="transform: rotate(-60deg); transform-origin: 315px 125.942px 0px;"/><path class="arrowbrg" d="M163,281.82685902179844 v -12 h 100 v -12 l 24 24 l -24 24 v -12 h -100 Z" fill="MediumSeaGreen" stroke="none" stroke-width="2" rotatedeg="-60" style="transform: rotate(-60deg); transform-origin: 225px 281.827px 0px;"/><path class="arrowbrg" d="M208,359.7691453623979 v -12 h 100 v -12 l 24 24 l -24 24 v -12 h -100 Z" fill="MediumSeaGreen" stroke="none" stroke-width="2" rotatedeg="0" style="transform: rotate(0deg); transform-origin: 270px 359.769px 0px;"/><path class="arrowbrg" d="M343,281.82685902179844 v -12 h 100 v -12 l 24 24 l -24 24 v -12 h -100 Z" fill="MediumSeaGreen" stroke="none" stroke-width="2" rotatedeg="120" style="transform: rotate(120deg); transform-origin: 405px 281.827px 0px;"/><path class="arrowbrg" d="M433,281.82685902179844 v -12 h 100 v -12 l 24 24 l -24 24 v -12 h -100 Z" fill="MediumSeaGreen" stroke="none" stroke-width="2" rotatedeg="60" style="transform: rotate(60deg); transform-origin: 495px 281.827px 0px;"/><path class="arrowbrg" d="M433,125.94228634059948 v -12 h 100 v -12 l 24 24 l -24 24 v -12 h -100 Z" fill="MediumSeaGreen" stroke="none" stroke-width="2" rotatedeg="120" style="transform: rotate(120deg); transform-origin: 495px 125.942px 0px;"/><path class="arrowbrg" d="M478,203.88457268119896 v -12 h 100 v -12 l 24 24 l -24 24 v -12 h -100 Z" fill="MediumSeaGreen" stroke="none" stroke-width="2" rotatedeg="180" style="transform: rotate(180deg); transform-origin: 540px 203.885px 0px;"/><path class="arrowbrg" d="M523,281.82685902179844 v -12 h 100 v -12 l 24 24 l -24 24 v -12 h -100 Z" fill="#cac272" stroke="none" stroke-width="2" rotatedeg="120" style="transform: rotate(120deg); transform-origin: 585px 281.827px 0px;"/><path class="arrowbrg" d="M343,125.94228634059948 v -12 h 100 v -12 l 24 24 l -24 24 v -12 h -100 Z" fill="#cac272" stroke="none" stroke-width="2" rotatedeg="-120" style="transform: rotate(-120deg); transform-origin: 405px 125.942px 0px;"/><path class="arrowbrg" d="M208,48 v -12 h 100 v -12 l 24 24 l -24 24 v -12 h -100 Z" fill="#cac272" stroke="none" stroke-width="2" rotatedeg="180" style="transform: rotate(180deg); transform-origin: 270px 48px 0px;"/><path class="arrowbrg" d="M163,125.94228634059948 v -12 h 100 v -12 l 24 24 l -24 24 v -12 h -100 Z" fill="#cac272" stroke="none" stroke-width="2" rotatedeg="60" style="transform: rotate(60deg); transform-origin: 225px 125.942px 0px;"/><path class="arrowbrg" d="M73,281.82685902179844 v -12 h 100 v -12 l 24 24 l -24 24 v -12 h -100 Z" fill="#cac272" stroke="none" stroke-width="2" rotatedeg="-120" style="transform: rotate(-120deg); transform-origin: 135px 281.827px 0px;"/><path class="arrowbrg" d="M73,125.94228634059948 v -12 h 100 v -12 l 24 24 l -24 24 v -12 h -100 Z" fill="#cac272" stroke="none" stroke-width="2" rotatedeg="-60" style="transform: rotate(-60deg); transform-origin: 135px 125.942px 0px;"/><path class="arrowbrg" d="M298,203.88457268119896 v -12 h 100 v -12 l 24 24 l -24 24 v -12 h -100 Z" fill="#cac272" stroke="none" stroke-width="2" rotatedeg="0" style="transform: rotate(0deg); transform-origin: 360px 203.885px 0px;"/></g>
				<path class="arrowbrg hide" d="M208,48 v -12 h 100 v -12 l 24 24 l -24 24 v -12 h -100 Z" fill="MediumSeaGreen" stroke="#333" stroke-width="2"/>
				<g class="bottomarea"><circle cx="90" cy="203.88457268119896" r="24" fill="#33d" stroke="none" stroke-width="4" i="3" stroke-dasharray="7 7" class="" style="transform: translate(0px);"/><circle cx="630" cy="203.88457268119896" r="24" fill="#3d3" stroke="none" stroke-width="4" i="6" stroke-dasharray="7 7" class="" style="transform: translate(0px);"/></g>
				<g class="middlearea">
					<path id="movingroute" class="route_tmp hide" d="M90,203.88457268119896 L 180,48 L 270,203.88457268119896 L 450,203.88457268119896 L 360,359.7691453623979 " fill="none" stroke="#333" stroke-linejoin="round" stroke-width="4" stroke-dasharray="12 7">
						<animate class="dashoffset" attributeName="stroke-dashoffset" values="0;-24000" repeatCount="indefinite" dur="777s"/>
					</path>
				</g>
				<g class="toparea"><circle cx="90" cy="203.88457268119896" r="24" fill="#33d" stroke="none" stroke-width="4" i="3" stroke-dasharray="7 7" class="hide" style="transform: translate(0px);"/><circle cx="630" cy="203.88457268119896" r="24" fill="#3d3" stroke="none" stroke-width="4" i="6" stroke-dasharray="7 7" class="hide" style="transform: translate(0px);"/></g>
				<use>
					<animateMotion dur="12s" fill="freeze" additive="sum" id="moveinroute" rotate="auto">
					<mpath xlink:href="#movingroute"/>
					</animateMotion>
				</use>
				<g id="g12155">
				<path d="M 48 420 h 72 v 40 h -72 Z" fill="none" stroke="#fff" stroke-width="2" id="path22"/>
				<path d="M 48 420 h 48 l -24 40 h -24 Z" fill="#33d" stroke="#fff" stroke-width="2" id="path24" class=""/>
				<path d="M 96 420 h 24 v 40 h -48 Z" fill="#3d3" stroke="#fff" stroke-width="2" id="path26" class="hide"/>
				</g>
				<g id="g320" transform="translate(38.455111,0.32714844)" class="hide">
				<circle cx="181.37874" cy="439.67285" r="16" fill="#b0dceb" stroke="#ffffff" stroke-width="2" id="circle33"/>
				<path style="fill:none;stroke:#8f2929;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="m 170.86887,429.26053 21.00272,21.00272" id="path102"/>
				<path style="fill:none;stroke:#8f2929;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="m 191.87159,429.26053 -21.00272,21.00272" id="path184"/>
				</g>
				<g id="g2370" transform="translate(36.617922,0.32714844)" class="hide">
				<circle cx="249.33466" cy="439.67285" r="16" fill="#b0dceb" stroke="#ffffff" stroke-width="2" id="circle322"/>
				<path style="fill:#8f2929;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="m 255.77156,427.55647 -19.50493,12.27039 19.58086,11.99501 z" id="path479"/>
				</g>
				<g id="g5495" transform="translate(35.32123,0.32714844)" class="hide">
				<circle cx="316.75009" cy="439.67285" r="16" fill="#b0dceb" stroke="#ffffff" stroke-width="2" id="circle2372"/>
				<path style="fill:none;stroke:#078f00;stroke-width:4.8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="m 307.45708,441.44638 6.70497,7.29655 12.7837,-18.1367" id="path2560"/>
				</g>
				<g id="g8717" transform="translate(38.455111,0.32714844)" class="hide">
				<circle cx="379.73492" cy="439.67285" r="16" fill="#b0dceb" stroke="#ffffff" stroke-width="2" id="circle5497"/>
				<path style="fill:#078f00;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="m 373.27064,427.6435 19.50493,12.27039 -19.58086,11.99501 z" id="path5939"/>
				</g>
				<g id="g11048" transform="translate(0,-2.7672119)" class="">
				<rect style="fill:#b0dceb;fill-opacity:1;stroke:#fff;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" id="rect7925" width="46.221607" height="27.315125" x="501.40842" y="429.10965"/>
				<path style="fill:none;stroke:#490f92;stroke-width:3.6;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="m 510.76874,434.58056 v 16.35487" id="path9124"/>
				<path style="fill:none;stroke:#490f92;stroke-width:3.6;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="m 526.0211,436.6151 -7.81082,6.64035 7.61586,6.43247" id="path9885"/>
				<path style="fill:none;stroke:#490f92;stroke-width:3.6;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="m 539.43881,436.6151 -7.81082,6.64035 7.61586,6.43247" id="path10346"/>
				</g>
				<g id="g11376" transform="matrix(-1,0,0,1,1169.14,-2.7672119)" class="hide">
				<rect style="fill:#b0dceb;fill-opacity:1;stroke:#fff;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" id="rect11368" width="46.221607" height="27.315125" x="501.40842" y="429.10965"/>
				<path style="fill:none;stroke:#490f92;stroke-width:3.6;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="m 510.76874,434.58056 v 16.35487" id="path11370"/>
				<path style="fill:none;stroke:#490f92;stroke-width:3.6;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="m 526.0211,436.6151 -7.81082,6.64035 7.61586,6.43247" id="path11372"/>
				<path style="fill:none;stroke:#490f92;stroke-width:3.6;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="m 539.43881,436.6151 -7.81082,6.64035 7.61586,6.43247" id="path11374"/>
				</g>
				<rect style="fill:#b0dceb;fill-opacity:1;stroke:#490f92;stroke-width:3.6;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" id="rect11400" width="21.318359" height="21.318359" x="576.03088" y="429.34082"/>
			</g><style xmlns="http://www.w3.org/1999/xhtml">
html, body {margin: 0; padding: 0; width: 100%; height: 100%;}
* {box-sizing: border-box;}
#titing {position: relative; width: 100%; height: 100%;}
#titing_innner {position: relative; padding-top: 66.7%;}
#konigsberg {position: absolute; float: left; background: lightblue; width: 100%; height: 100%; top: 0; left: 0;}
.arrowbrg {transition: transform 1.2s;}
.man {transition: transform 1s linear;}
#detail_div {position: absolute; top: 0; left: 0; color: #333; font-size: 18px; z-index: 7;}
#setting_div {position: fixed; top: 0; left: 0; color: #333; font-size: 18px; width: 100%; height: 100%; z-index: 10;}
#setting_close {position: absolute; bottom: 12px; right: 12px; width: 24px; height: 24px; z-index: 12; line-height: 24px; text-align: center; background: #333; color: white; cursor: pointer;}
#setting_text {position: absolute; top: 0; left: 0; color: #333; font-size: 18px; width: 100%; height: 100%; padding: 12px; line-height: 1.5; resize: none;}
#fullscreen {position: absolute; z-index: 7; top: 7px; right: 7px; width: 7%; height: 7%; background: #8278f5; cursor: pointer;}
.rotate90 {transform: rotate(90deg) !important;}
.hide {display: none;}
	</style></svg>


藍方該如何走才會贏?
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5043
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 174 次
被圈友点赞: 204 次
联系:

Re: 改下哥尼斯堡網頁程式

帖子 ejsoon »

代码: 全选

<svg width="1200" height="600" viewBox="0 0 1200 600" xmlns="http://www.w3.org/2000/svg">
  <rect width="1200" height="600" fill="#fcfcfc" />

  <text x="600" y="50" text-anchor="middle" font-size="32" font-family="Arial, sans-serif" font-weight="bold">
    14種木頭與鋼鐵顏色
  </text>

  <text x="300" y="100" text-anchor="middle" font-size="24" font-family="Arial, sans-serif" font-weight="bold" fill="#5D4037">
    木頭顏色 (7種)
  </text>

  <circle cx="150" cy="180" r="40" fill="#F5E0A0" stroke="#ccc" />
  <text x="150" y="240" text-anchor="middle" font-size="16" fill="#333" font-family="Arial, sans-serif">
    橡木 (Oak)
  </text>
  <text x="150" y="260" text-anchor="middle" font-size="14" fill="#666" font-family="Arial, sans-serif">
    #F5E0A0
  </text>

  <circle cx="300" cy="180" r="40" fill="#EED696" stroke="#ccc" />
  <text x="300" y="240" text-anchor="middle" font-size="16" fill="#333" font-family="Arial, sans-serif">
    楓木 (Maple)
  </text>
  <text x="300" y="260" text-anchor="middle" font-size="14" fill="#666" font-family="Arial, sans-serif">
    #EED696
  </text>

  <circle cx="450" cy="180" r="40" fill="#C19A6B" stroke="#ccc" />
  <text x="450" y="240" text-anchor="middle" font-size="16" fill="#333" font-family="Arial, sans-serif">
    松木 (Pine)
  </text>
  <text x="450" y="260" text-anchor="middle" font-size="14" fill="#666" font-family="Arial, sans-serif">
    #C19A6B
  </text>

  <circle cx="150" cy="360" r="40" fill="#A86438" stroke="#ccc" />
  <text x="150" y="420" text-anchor="middle" font-size="16" fill="#333" font-family="Arial, sans-serif">
    櫻桃木 (Cherry Wood)
  </text>
  <text x="150" y="440" text-anchor="middle" font-size="14" fill="#666" font-family="Arial, sans-serif">
    #A86438
  </text>

  <circle cx="300" cy="360" r="40" fill="#8B4F29" stroke="#ccc" />
  <text x="300" y="420" text-anchor="middle" font-size="16" fill="#333" font-family="Arial, sans-serif">
    桃花心木 (Mahogany)
  </text>
  <text x="300" y="440" text-anchor="middle" font-size="14" fill="#666" font-family="Arial, sans-serif">
    #8B4F29
  </text>

  <circle cx="450" cy="360" r="40" fill="#5D4037" stroke="#ccc" />
  <text x="450" y="420" text-anchor="middle" font-size="16" fill="#333" font-family="Arial, sans-serif">
    胡桃木 (Walnut)
  </text>
  <text x="450" y="440" text-anchor="middle" font-size="14" fill="#666" font-family="Arial, sans-serif">
    #5D4037
  </text>

  <circle cx="300" cy="500" r="40" fill="#B0723A" stroke="#ccc" />
  <text x="300" y="560" text-anchor="middle" font-size="16" fill="#333" font-family="Arial, sans-serif">
    柚木 (Teak)
  </text>
  <text x="300" y="580" text-anchor="middle" font-size="14" fill="#666" font-family="Arial, sans-serif">
    #B0723A
  </text>

  <text x="900" y="100" text-anchor="middle" font-size="24" font-family="Arial, sans-serif" font-weight="bold" fill="#3C3C3C">
    鋼鐵顏色 (7種)
  </text>

  <circle cx="750" cy="180" r="40" fill="#E6E6E6" stroke="#ccc" />
  <text x="750" y="240" text-anchor="middle" font-size="16" fill="#333" font-family="Arial, sans-serif">
    拋光不銹鋼 (Polished Stainless)
  </text>
  <text x="750" y="260" text-anchor="middle" font-size="14" fill="#666" font-family="Arial, sans-serif">
    #E6E6E6
  </text>

  <circle cx="900" cy="180" r="40" fill="#C0C0C0" stroke="#ccc" />
  <text x="900" y="240" text-anchor="middle" font-size="16" fill="#333" font-family="Arial, sans-serif">
    刷紋不銹鋼 (Brushed Stainless)
  </text>
  <text x="900" y="260" text-anchor="middle" font-size="14" fill="#666" font-family="Arial, sans-serif">
    #C0C0C0
  </text>

  <circle cx="1050" cy="180" r="40" fill="#696969" stroke="#ccc" />
  <text x="1050" y="240" text-anchor="middle" font-size="16" fill="#333" font-family="Arial, sans-serif">
    碳鋼 (Carbon Steel)
  </text>
  <text x="1050" y="260" text-anchor="middle" font-size="14" fill="#666" font-family="Arial, sans-serif">
    #696969
  </text>

  <circle cx="750" cy="360" r="40" fill="#B8B8B8" stroke="#ccc" />
  <text x="750" y="420" text-anchor="middle" font-size="16" fill="#333" font-family="Arial, sans-serif">
    鍍鋅鋼 (Galvanized Steel)
  </text>
  <text x="750" y="440" text-anchor="middle" font-size="14" fill="#666" font-family="Arial, sans-serif">
    #B8B8B8
  </text>

  <circle cx="900" cy="360" r="40" fill="#3C3C3C" stroke="#ccc" />
  <text x="900" y="420" text-anchor="middle" font-size="16" fill="#333" font-family="Arial, sans-serif">
    鑄鐵 (Cast Iron)
  </text>
  <text x="900" y="440" text-anchor="middle" font-size="14" fill="#666" font-family="Arial, sans-serif">
    #3C3C3C
  </text>

  <circle cx="1050" cy="360" r="40" fill="#A9A9A9" stroke="#ccc" />
  <text x="1050" y="420" text-anchor="middle" font-size="16" fill="#333" font-family="Arial, sans-serif">
    鋼坯 (Steel Billets)
  </text>
  <text x="1050" y="440" text-anchor="middle" font-size="14" fill="#666" font-family="Arial, sans-serif">
    #A9A9A9
  </text>

  <circle cx="900" cy="500" r="40" fill="#708090" stroke="#ccc" />
  <text x="900" y="560" text-anchor="middle" font-size="16" fill="#333" font-family="Arial, sans-serif">
    熱軋鋼 (Hot-Rolled Steel)
  </text>
  <text x="900" y="580" text-anchor="middle" font-size="14" fill="#666" font-family="Arial, sans-serif">
    #708090
  </text>

</svg>
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5043
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 174 次
被圈友点赞: 204 次
联系:

Re: 改下哥尼斯堡網頁程式

帖子 ejsoon »

簡述蒙地卡洛樹搜尋,在弈棋上的應用方式。

代码: 全选

**蒙地卡洛樹搜尋(Monte Carlo Tree Search, MCTS)** 是現代弈棋 AI(如著名的 AlphaGo)的核心算法。與傳統那種硬碰硬、計算所有可能性的「暴力搜索」不同,MCTS 更像是一個聰明的試錯者。

簡單來說,它透過**隨機模擬**來評估棋局的優劣,而不是依賴人類預設的複雜評分表。

---

## MCTS 的四大步驟

MCTS 是一個循環迭代的過程,每一步棋通常會進行數千甚至數萬次的以下四個步驟:

| 步驟 | 動作名稱 | 具體描述 |
| :--- | :--- | :--- |
| **1** | **選擇 (Selection)** | 從根節點開始,根據「勝率」與「探索次數」選擇最值得投入的子路徑,直到抵達目前的葉子節點。 |
| **2** | **擴展 (Expansion)** | 如果目前的節點還沒分出勝負,就增加一個或多個新的子節點,代表下一步可能的走法。 |
| **3** | **模擬 (Simulation)** | 從新節點開始,以隨機或半隨機的方式「快進」直到棋局結束,看最後是誰贏了。 |
| **4** | **回傳 (Backpropagation)** | 將模擬的勝負結果傳回路徑上的所有節點,更新它們的統計數據(勝率與訪問次數)。 |



---

## 為什麼它在弈棋上這麼強?

在 MCTS 出現之前,電腦下圍棋一直很弱,主要原因是圍棋的**分支因子(每步可能的選擇)**太大,且很難寫出準確的評估函式。MCTS 解決了這些痛點:

* **不需要完美的評估函式:** 電腦不需要知道這步棋「幾分」,它只需要知道這步棋「模擬 1000 次後贏了 800 次」。
* **動態分配計算資源:** 透過 **UCT (Upper Confidence Bound applied to Trees)** 公式,演算法能在「開發已知的強手」與「探索未知的潛在好手」之間取得平衡。

其核心公式通常表示為:

$$UCT = \frac{w_i}{n_i} + c \sqrt{\frac{\ln N}{n_i}}$$

* $\frac{w_i}{n_i}$:目前的勝率(Explexploitation,開發)。
* $c \sqrt{\frac{\ln N}{n_i}}$:探索紅利(Exploration,探索),$c$ 是常數,$N$ 是總次數,$n_i$ 是該路徑次數。

---

## 現代的進化:MCTS + 神經網路

現在最強的 AI(如 AlphaZero)已經不再使用單純的「隨機模擬」了。它們改用**深度神經網路**來:
1.  **縮小搜索寬度:** 直接告訴 MCTS 哪些棋步看起來最像高手會下的(Policy Network)。
2.  **縮短搜索深度:** 直接預測當前局面的勝率,不再需要把棋下完(Value Network)。

這讓 MCTS 從「亂槍打鳥」變成了「精準打擊」。

如果你對其中某個步驟(例如那個數學公式怎麼運作的)感興趣,或者想知道它跟傳統 Minimax 演算法的具體差別,隨時跟我說!
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5043
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 174 次
被圈友点赞: 204 次
联系:

Re: 改下哥尼斯堡網頁程式

帖子 ejsoon »

改變men放在bottom和top的邏輯,不再有appendChild和remove,使之動畫不會再出現第一步直接停在終點的問題。把fullscreenbtn的right的2改成7,以使其不超出屏幕。
konigsberg7772.7z
(7.38 KiB) 已下载 3 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5043
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 174 次
被圈友点赞: 204 次
联系:

Re: 改下哥尼斯堡網頁程式

帖子 ejsoon »

代码: 全选

這是一個用來預測移動結果的 JavaScript 函式。

根據遊戲原本的邏輯,點擊 `btn_finish` 觸發移動後,會先播放動畫,接著翻轉路徑上的橋樑方向(`turnBridge`),最後才會判斷勝負條件與射擊(`judgeResult`)。

這個函式會**提前模擬**動畫結束後的橋樑翻轉狀態,以 BFS (廣度優先搜尋) 來判斷下回合對手是否能透過新的橋樑方向找到連通的路徑射擊當前玩家,並同時判斷是否有違規或平局。

你可以直接將以下程式碼貼上瀏覽器的 Console 中執行:

```javascript
function predictOutcome() {
    // 確保目前是在玩家規劃路徑的階段 (2: Blue time, 4: Green time)
    if (game_status !== 2 && game_status !== 4) return; 

    let activePlayer = current_man;
    let nextPlayer = ~current_man & 1;

    let activeManEnd = route_tmp[route_tmp.length - 1]; // 當前玩家移動的終點
    let nextPlayerPos = parseInt(men_bottom[nextPlayer].getAttribute("i")); // 對手的位置

    let activeColor = activePlayer ? "Green (綠)" : "Blue (藍)";
    let nextColor = nextPlayer ? "Green (綠)" : "Blue (藍)";

    console.log(`--- 模擬 ${activeColor} 點擊 Finish 後的結局 ---`);

    // 1. 檢查基本規則敗北條件 (同 judgeResult)
    if (!(route_tmp.length > 1 && (new_arrow.length > 0 || arrows.length >= 12))) {
        let reason = route_tmp.length <= 1 ? "沒有移動任何步數" : "沒有建立新的橋樑";
        console.log(`[結局] ❌ 違規敗北:${activeColor} 將會輸掉遊戲,因為${reason}。`);
        return;
    }

    // 2. 深度拷貝當前的箭頭地圖,準備模擬 turnBridge() 的翻轉效果
    let sim_arrow_map = JSON.parse(JSON.stringify(arrow_map));
    let sim_stack_arrow = stack_arrow.concat(new_arrow);

    for (let x = 0; x < route_tmp.length - 1; x++) {
        let step_start = route_tmp[x];
        let step_end = route_tmp[x + 1];

        // 尋找路徑上是否有順向的橋樑,有的話將其翻轉
        for (let y = 0; y * 2 < sim_stack_arrow.length && y < 12; y++) {
            if (sim_stack_arrow[y * 2] === step_start && sim_stack_arrow[y * 2 + 1] === step_end) {
                // 在地圖中翻轉方向
                let turnindex = sim_arrow_map[step_start].indexOf(step_end);
                if (turnindex > -1) {
                    sim_arrow_map[step_start].splice(turnindex, 1);
                    sim_arrow_map[step_end].push(step_start);
                }
                // 更新 stack_arrow 狀態
                sim_stack_arrow[y * 2] = step_end;
                sim_stack_arrow[y * 2 + 1] = step_start;
                break;
            }
        }
    }

    // 3. 檢查平局條件 (Draw) - 歷史盤面重複
    let sim_men0_i = activePlayer === 0 ? activeManEnd : parseInt(men_bottom[0].getAttribute("i"));
    let sim_men1_i = activePlayer === 1 ? activeManEnd : parseInt(men_bottom[1].getAttribute("i"));
    let current_history = [sim_men0_i, sim_men1_i].concat(sim_stack_arrow);
    let current_history_str = JSON.stringify(current_history);

    if (game_history.indexOf(current_history_str) !== -1) {
        console.log(`[結局] 🤝 平局 (Draw):移動後盤面狀態與歷史紀錄重複。`);
        return;
    }

    // 4. 檢查射擊 (Shooting) 條件 - 對手能否順著新的方向走到你的終點
    let dots_array = [nextPlayerPos];
    let canShoot = false;

    for (let x = 0; x < dots_array.length; x++) {
        let current_dot = dots_array[x];
        let connections = sim_arrow_map[current_dot] || [];
        for (let y = 0; y < connections.length; y++) {
            let new_dot = connections[y];
            if (new_dot === activeManEnd) {
                canShoot = true; // 發現可以連通到當前玩家的終點
            }
            if (dots_array.indexOf(new_dot) === -1) {
                dots_array.push(new_dot); // 加入佇列繼續展開
            }
        }
    }

    // 輸出最終射擊預測結果
    if (canShoot) {
        console.log(`[結局] 💀 致命失誤!${activeColor} 移動到節點 ${activeManEnd} 後,會透過橋樑與對手連通,將被 ${nextColor} 射擊 (Shooting) 擊殺!`);
    } else {
        console.log(`[結局] 🛡️ 安全。${activeColor} 完美規避了路線,換 ${nextColor} 回合。`);
    }
}

// 綁定到原本的 btn_finish 按鈕上
// 當你點擊 Finish 時,它會搶先輸出結果到 Console
btn_finish.addEventListener('click', predictOutcome);
```

### **如何運作**
1. 將這段代碼直接貼在 F12 的 Console 中(或直接寫進 `<script>` 的尾端)。
2. 在遊戲中照常選好路線,當 **「勾勾 (Finish) 按鈕」** 出現時,只要一點擊它。
3. 動畫開始前,Console 就會提早劇透你這個操作是「安全」、「被射死」、「犯規」還是「平手」。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5043
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 174 次
被圈友点赞: 204 次
联系:

Re: 改下哥尼斯堡網頁程式

帖子 ejsoon »

運用你做好的predictOutcome函式,為某一方的下一步做蒙地卡洛樹搜尋。當點擊id="g12155"時開始搜尋,在console給出搜尋結果。如有哪些需要調整的參數請告知。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5043
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 174 次
被圈友点赞: 204 次
联系:

Re: 改下哥尼斯堡網頁程式

帖子 ejsoon »

gemini pro:

代码: 全选

這是一個為這個遊戲量身打造的 **Flat Monte Carlo (蒙地卡洛模擬)** 腳本。

因為瀏覽器 Console 執行完整的深度蒙地卡洛樹搜尋(MCTS)容易導致網頁卡死,所以我採用了更適合這個環境的**廣度優先路徑展開 + 隨機模擬 (Playouts)** 的方法。腳本會先用類似 `predictOutcome` 的邏輯篩選掉「走完立刻被射殺」的致命步,接著對剩下安全的路線進行大量的隨機推演,最終給出預期勝率最高的推薦路線。

你可以將以下程式碼直接貼到瀏覽器的 Console 執行。執行後,只要**點擊左下角的圖示 (`id="g12155"`)**,就會在 Console 印出當前最佳的頭幾步路徑推薦。

### 蒙地卡洛搜尋腳本

```javascript
// 綁定點擊事件到目標元素 g12155
document.getElementById("g12155").addEventListener('click', runMCTS);

function runMCTS() {
    // 確保目前是在遊戲進行中的狀態
    if (![1, 2, 3, 4].includes(game_status)) {
        console.warn("⚠️ 請在輪到玩家移動時,再點擊圖示執行搜尋。");
        return;
    }

    let activeColor = current_man ? "Green (綠)" : "Blue (藍)";
    console.log(`\n🔍 開始為 ${activeColor} 執行蒙地卡洛搜尋... 請稍候幾秒鐘`);

    // ==========================================
    // 🛠️ 可調整的 MCTS 參數
    // ==========================================
    const PARAMS = {
        // 每個「安全候選路徑」要進行幾場隨機模擬盤? (數字越大越準,但計算越久)
        SIMULATIONS_PER_MOVE: 50, 
        // 每次隨機模擬最多往後推演幾回合? (超過就視為平手)
        MAX_DEPTH: 8,            
        // 單一回合內,允許走到幾個節點? (避免 DFS 尋找合法路徑時發生組合爆炸)
        MAX_PATH_LENGTH: 5       
    };

    // 取得當前遊戲狀態 (深度拷貝以利沙盤推演)
    let rootState = {
        p0: parseInt(men_bottom[0].getAttribute("i") || men_top[0].getAttribute("i")),
        p1: parseInt(men_bottom[1].getAttribute("i") || men_top[1].getAttribute("i")),
        turn: current_man,
        empty_map: JSON.parse(JSON.stringify(empty_map)),
        arrow_map: JSON.parse(JSON.stringify(arrow_map)),
        stack_arrow: stack_arrow.slice()
    };

    // 1. 取得當下所有合法的路徑組合
    let rootMoves = getLegalMoves(rootState, PARAMS.MAX_PATH_LENGTH);
    if (rootMoves.length === 0) {
        console.log("❌ 已經無路可走 (找不到任何合法的新橋樑路線)。");
        return;
    }

    let moveStats = [];

    // 2. 評估第一步,並進入蒙地卡洛模擬
    for (let move of rootMoves) {
        // 模擬走這一步的結果 (結合 predictOutcome 的邏輯)
        let res = applyMove(rootState, move);
        
        if (res.terminal) {
            // 如果這步走完馬上被對方射擊,給予極低分數
            moveStats.push({ move, wins: -999, plays: 1, immediateLoss: true });
        } else {
            // 安全的步,進行隨機模擬 (Playouts)
            let wins = 0;
            for (let i = 0; i < PARAMS.SIMULATIONS_PER_MOVE; i++) {
                let winner = playout(res.state, PARAMS.MAX_DEPTH, PARAMS.MAX_PATH_LENGTH);
                if (winner === rootState.turn) wins += 1;          // 贏得 1 分
                else if (winner === -1) wins += 0.5;               // 平手得 0.5 分
            }
            moveStats.push({ move, wins, plays: PARAMS.SIMULATIONS_PER_MOVE });
        }
    }

    // 3. 根據勝率排序結果
    moveStats.sort((a, b) => (b.wins / b.plays) - (a.wins / a.plays));

    // 4. 輸出報告
    console.log(`✅ 搜尋完成!(共推演 ${rootMoves.length} 條可能路徑)`);
    console.log("🏆 推薦路徑排行榜 (前 5 名):");
    
    moveStats.slice(0, 5).forEach((stat, idx) => {
        let pathStr = stat.move.join(" ➔ ");
        if (stat.immediateLoss) {
            console.log(`   ${idx + 1}. 路徑: [${pathStr}] | 💀 致命步 (走完立刻遭對手連通射擊)`);
        } else {
            let score = ((stat.wins / stat.plays) * 100).toFixed(1);
            console.log(`   ${idx + 1}. 路徑: [${pathStr}] | 預期勝率: ${score}% (模擬 ${stat.plays} 場)`);
        }
    });
}

// ---------------------------------------------------------
// 內部核心邏輯區 (負責無 UI 的沙盤推演)
// ---------------------------------------------------------

// 尋找某個狀態下的所有合法路徑
function getLegalMoves(state, maxLen) {
    let start = state.turn === 0 ? state.p0 : state.p1;
    let moves = [];
    let bridges_count = state.stack_arrow.length / 2;

    function dfs(curr, path, has_new_bridge) {
        if (path.length > 1 && (has_new_bridge || bridges_count >= 12)) {
            moves.push(path.slice());
        }
        if (path.length >= maxLen) return;

        for (let nxt of state.empty_map[curr] || []) {
            if (path.indexOf(nxt) === -1) {
                path.push(nxt);
                dfs(nxt, path, true);
                path.pop();
            }
        }
        for (let nxt of state.arrow_map[curr] || []) {
            if (path.indexOf(nxt) === -1) {
                path.push(nxt);
                dfs(nxt, path, has_new_bridge);
                path.pop();
            }
        }
    }
    dfs(start, [start], false);
    return moves;
}

// 將某條路徑套用到當前狀態,翻轉橋樑,並判斷是否立刻分出勝負
function applyMove(state, route) {
    let nextState = {
        p0: state.p0, p1: state.p1,
        turn: 1 - state.turn,
        empty_map: JSON.parse(JSON.stringify(state.empty_map)),
        arrow_map: JSON.parse(JSON.stringify(state.arrow_map)),
        stack_arrow: state.stack_arrow.slice()
    };

    let new_arrow = [];
    // 建立新橋樑
    for (let i = 0; i < route.length - 1; i++) {
        let u = route[i], v = route[i + 1];
        let eIdx = nextState.empty_map[u].indexOf(v);
        if (eIdx > -1) {
            nextState.empty_map[u].splice(eIdx, 1);
            nextState.empty_map[v].splice(nextState.empty_map[v].indexOf(u), 1);
            nextState.arrow_map[u].push(v);
            new_arrow.push(u, v);
        }
    }

    // 翻轉路徑上的順向橋樑
    let sim_stack_arrow = nextState.stack_arrow.concat(new_arrow);
    for (let x = 0; x < route.length - 1; x++) {
        let step_start = route[x], step_end = route[x + 1];
        for (let y = 0; y * 2 < sim_stack_arrow.length && y < 12; y++) {
            if (sim_stack_arrow[y * 2] === step_start && sim_stack_arrow[y * 2 + 1] === step_end) {
                let turnindex = nextState.arrow_map[step_start].indexOf(step_end);
                if (turnindex > -1) {
                    nextState.arrow_map[step_start].splice(turnindex, 1);
                    nextState.arrow_map[step_end].push(step_start);
                }
                sim_stack_arrow[y * 2] = step_end;
                sim_stack_arrow[y * 2 + 1] = step_start;
                break;
            }
        }
    }
    nextState.stack_arrow = sim_stack_arrow.slice(0, 24);

    // 更新位置
    if (state.turn === 0) nextState.p0 = route[route.length - 1];
    else nextState.p1 = route[route.length - 1];

    // 檢查對手是否能射擊你 (BFS 尋找連通性)
    let target = state.turn === 0 ? nextState.p0 : nextState.p1;
    let start = state.turn === 0 ? nextState.p1 : nextState.p0;
    let canShoot = false;
    let q = [start];
    let visited = new Set([start]);

    while (q.length > 0) {
        let curr = q.shift();
        for (let nxt of (nextState.arrow_map[curr] || [])) {
            if (nxt === target) { canShoot = true; break; }
            if (!visited.has(nxt)) {
                visited.add(nxt);
                q.push(nxt);
            }
        }
        if (canShoot) break;
    }

    if (canShoot) return { terminal: true, winner: 1 - state.turn }; // 對手獲勝
    return { terminal: false, state: nextState };
}

// 隨機推演 (Playouts) 直到觸發深度限制或有人獲勝
function playout(state, maxDepth, maxPathLen) {
    let currState = state;
    for (let d = 0; d < maxDepth; d++) {
        let moves = getLegalMoves(currState, maxPathLen);
        if (moves.length === 0) {
            return 1 - currState.turn; // 走投無路,換對方贏
        }
        // 完全隨機選一步
        let randMove = moves[Math.floor(Math.random() * moves.length)];
        let result = applyMove(currState, randMove);
        if (result.terminal) {
            return result.winner;
        }
        currState = result.state;
    }
    return -1; // 達到限制深度,視為平局
}
```

### 關於可調整的參數 (PARAMS)

我在腳本頂部設計了一個 `PARAMS` 物件,你可以根據你的電腦效能直接修改裡面的數字:

1.  **`SIMULATIONS_PER_MOVE` (預設 50)**:
    * 這決定了每條潛在路線要模擬幾次。數字越大,得出的勝率越可靠;但如果調到 `500` 以上,瀏覽器可能會短暫卡頓幾秒鐘。
2.  **`MAX_DEPTH` (預設 8)**:
    * MCTS 每模擬一局會往後推演幾回合。如果設得太深(例如 `20`),會消耗大量運算資源;設為 `8` 算是夠預測近期的戰況,超時就當作平局 (Draw)。
3.  **`MAX_PATH_LENGTH` (預設 5)**:
    * 一次移動中,最多可以走過幾個點。因為這個遊戲的點陣互通性很高,如果不限制路徑長度,DFS 窮舉合法路徑時會產生嚴重的「組合爆炸」。通常一回合走 `3~5` 個節點已經是非常複雜的操作了,設 `5` 能有效控制記憶體。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
回复

在线用户

正浏览此版面之用户: Amazon [Bot] 和 0 访客