0%

I make a samll game about greedy snake and I met some problem. I want to moving background so that can simulate the center snake is moving. But there isn’t possible to change the body translate each frame.

Solution:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 世界座標偏移
// 螢幕位置 = 世界位置 - camera 偏移量
// 世界位置 = 螢幕位置 + camera 偏移量
// camera相當於玩家絕對坐標
const camera = { x: 0, y: 0 };

function moving(){
const x = currentMousePosition.x - width / 2;
const y = currentMousePosition.y - height / 2;
const angle = Math.atan2(y, x)
const dx = Math.cos(angle) * speed;
const dy = Math.sin(angle) * speed;
camera.x += dx;
camera.y += dy;
}

Not matter what interactive function, both can using World Position to detect.
When we want to show somthing on the screen, just change the World Position into the Screen Position by using formula.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function drawGrid(){
const gridSize = 50;
ctx.strokeStyle = colors.grid;
ctx.lineWidth = 1;
for(let x = -camera.x % gridSize; x < width; x += gridSize){
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, height);
ctx.stroke();
ctx.closePath();
}
for(let y = -camera.y % gridSize; y < height; y += gridSize){
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(width, y);
ctx.stroke();
ctx.closePath();
}
}

Drawing grid is a good way to simulate background moving.

-camera%gridSize 是判断從一個格子的甚麼位置開始畫

乘負號是因為玩家向右移,背景就要向左移。

When camera is increasing, -camera%gridSize should be decreasing.

This is my project below

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>greedy snake</title>
</head>

<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
--background-color: #2C3E50;
--snake-color: #4CAF50;
--food-color: #E74C3C;
}
body {
min-height: 100vh;
min-width: 100vw;
background-color: var(--background-color);
}
</style>

<body>
<canvas id="mycanvas"></canvas>
</body>
<script>
const colors = {
background: '#2c3e50',
snake: '#42b983',
food: '#e74c3c',
grid: '#34495e',
};

const canvas = document.getElementById('mycanvas');
const ctx = canvas.getContext('2d');
const body = document.body;

let width = canvas.width = window.innerWidth;
let height = canvas.height = window.innerHeight;

const currentMousePosition = { x: height / 2, y: width / 2 };


// 世界座標偏移
// 螢幕位置 = 世界位置 - camera 偏移量
// 世界位置 = 螢幕位置 + camera 偏移量
const camera = { x: 0, y: 0 };

const speed = 2;
const snakeSize = 16;
const foodSize = 6;

const snakePath = []; // 紀錄蛇頭的世界座標路徑(每幀)
let totalSegments = 1; // 身體節點數量
const segmentDistance = 8; // 身體每個節點之間的間隔距離(每隔8幀才是第二個身體節點)

const aiSnakes = [];
const numAiSnakes = 10; // AI 蛇的數量

for (let i = 0; i < numAiSnakes; i++) {
aiSnakes.push({
worldX: (Math.random() - 0.5) * 2000,
worldY: (Math.random() - 0.5) * 2000,
angle: Math.random() * Math.PI * 2,
speed: 1, // AI 稍微慢一點
snakePath: [],
totalSegments: 10,
color: `hsl(${Math.random() * 360}, 70%, 50%)` // 隨機顏色
});
}


const foots = [];
// 隨機生成新食物
for(let i = 0; i < 50; i++){
foots.push({
x: (Math.random() - 0.5) * 2000,
y: (Math.random() - 0.5) * 2000
});
}

window.addEventListener('mousemove', (e) => {
currentMousePosition.x = e.clientX;
currentMousePosition.y = e.clientY;
});

function drawGrid(){
const gridSize = 50;
console.log(-camera.x % gridSize);
ctx.strokeStyle = colors.grid;
ctx.lineWidth = 1;
for(let x = -camera.x % gridSize; x < width; x += gridSize){
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, height);
ctx.stroke();
ctx.closePath();
}
for(let y = -camera.y % gridSize; y < height; y += gridSize){
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(width, y);
ctx.stroke();
ctx.closePath();
}

}

function redraw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// draw background
drawGrid();

aiSnakes.forEach(ai => {
for (let i = ai.totalSegments - 1; i >= 0; i--) {
const pathIndex = i * segmentDistance;
const point = ai.snakePath[pathIndex];
if (point) {
// AI 世界座標 - 相機偏移 = 螢幕位置
const screenX = point.worldX - camera.x - snakeSize / 2;
const screenY = point.worldY - camera.y - snakeSize / 2;

ctx.fillStyle = i === 0 ? ai.color : ai.color + "88"; // 身體半透明
ctx.fillRect(screenX, screenY, snakeSize, snakeSize);
}
}
});

// 繪製蛇身
for (let i = 0; i < totalSegments; i++) {
// 取得該節點在路徑中的索引
const pathIndex = i * segmentDistance;
const point = snakePath[pathIndex];
if (point) {
// 將「世界座標」轉換回「螢幕座標」進行繪製
const screenX = point.worldX - camera.x;
const screenY = point.worldY - camera.y;

ctx.fillStyle = i === 0 ? colors.snake : '#388E3C'; // 頭用亮色,身體深色
ctx.fillRect(screenX, screenY, snakeSize, snakeSize);
}
}
// draw foots
foots.forEach(foot => {
ctx.fillStyle = colors.food;
ctx.beginPath();
ctx.arc(foot.x - camera.x, foot.y - camera.y, foodSize/2, 0, Math.PI * 2);
ctx.fill();
ctx.closePath();
});
}

function updateAi() {
aiSnakes.forEach(ai => {
// 1. 隨機改變方向 (每幀有 2% 的機率轉彎)
if (Math.random() < 0.02) {
ai.angle += (Math.random() - 0.5) * 2;
}

// 2. 根據角度更新世界座標
ai.worldX += Math.cos(ai.angle) * ai.speed;
ai.worldY += Math.sin(ai.angle) * ai.speed;

// 3. 記錄路徑 (用於畫身體)
ai.snakePath.unshift({ worldX: ai.worldX, worldY: ai.worldY });

if (ai.snakePath.length > ai.totalSegments * segmentDistance) {
ai.snakePath.pop();
}

// 4. AI 也可以吃食物
foots.forEach((foot, index) => {
const dx = ai.worldX - foot.x;
const dy = ai.worldY - foot.y;
const dist = Math.sqrt(dx * dx + dy * dy);

if (dist < snakeSize) {
foots.splice(index, 1);
ai.totalSegments += 2;
// 補償食物
foots.push({
x: (Math.random() - 0.5) * 2000,
y: (Math.random() - 0.5) * 2000
});
}
});
});
}

function moving(){
const x = currentMousePosition.x - width / 2;
const y = currentMousePosition.y - height / 2;
const angle = Math.atan2(y, x)
const dx = Math.cos(angle) * speed;
const dy = Math.sin(angle) * speed;
// console.log(`Angle: ${angle}, dx: ${dx}, dy: ${dy}`);
camera.x += dx;
camera.y += dy;

snakePath.unshift({
worldX: width / 2 + camera.x,
worldY: height / 2 + camera.y
});
if(snakePath.length > totalSegments * segmentDistance){
snakePath.pop();
}

}

function eating(){
const head = snakePath[0];
foots.forEach((foot, index) => {
if(head.worldX < foot.x + foodSize && head.worldX + snakeSize > foot.x &&
head.worldY < foot.y + foodSize && head.worldY + snakeSize > foot.y){
foots.splice(index, 1);
// 增加蛇的長度
totalSegments += 5;

for(let i = 0; i < 5; i++){
// 隨機生成新食物
foots.push({
x: (Math.random() - 0.5) * 2000,
y: (Math.random() - 0.5) * 2000
});
}
}
});


}

function checkCollisions() {
const playerWorldHead = { x: snakePath[0].worldX, y: snakePath[0].worldY };

aiSnakes.forEach((ai, aiIndex) => {
// --- 玩家撞到 AI 的身體 ---
// 跳過 AI 的頭部幾節,避免誤判
for (let i = segmentDistance * 2; i < ai.snakePath.length; i += segmentDistance) {
const segment = ai.snakePath[i];
const dx = playerWorldHead.x - segment.worldX;
const dy = playerWorldHead.y - segment.worldY;
const distance = Math.sqrt(dx * dx + dy * dy);

if (distance < snakeSize) {
// 玩家死亡
alert("遊戲結束!你被 AI 蛇吃掉了。");
setTimeout(() => {
location.reload(); // 簡單處理:重新整理網頁
}, 0);
}
}

// --- AI 撞到玩家的身體 ---
const aiWorldHead = { x: ai.worldX, y: ai.worldY };
for (let i = segmentDistance * 2; i < snakePath.length; i += segmentDistance) {
const segment = snakePath[i];
const dx = aiWorldHead.x - segment.worldX;
const dy = aiWorldHead.y - segment.worldY;
const distance = Math.sqrt(dx * dx + dy * dy);

if (distance < snakeSize) {
// AI 死亡
for(let j = 0; j < ai.snakePath.length; j++){
// 玩家吃掉 AI 後,生成一些食物作為獎勵
foots.push({
x: ai.snakePath[j].worldX,
y: ai.snakePath[j].worldY
});
}
aiSnakes.splice(aiIndex, 1);
spawnNewAi(); // 生成新 AI 補充
return;
}
}
});
}

// 生成新的 AI 蛇
function spawnNewAi() {
aiSnakes.push({
worldX: (Math.random() - 0.5) * 2000,
worldY: (Math.random() - 0.5) * 2000,
angle: Math.random() * Math.PI * 2,
speed: 1,
snakePath: [],
totalSegments: 10,
color: `hsl(${Math.random() * 360}, 70%, 50%)`
});
}


function repeating(){
redraw();
moving();
updateAi();
checkCollisions();
eating();
requestAnimationFrame(repeating);
}

const frameId = requestAnimationFrame(repeating);

// 监听窗口大小变化
window.addEventListener('resize', () => {
width = canvas.width = window.innerWidth;
height = canvas.height = window.innerHeight;
});

</script>
</html>

if you build a project which inculding mysql, and you want to use Aminder to control the database but don’t want to see others irrelevant database, you can read the Aminder official webside, download Adminer.php. Then create a editor.php, and put these two file into a independent folder.

the content of editor.php below

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<?php
function adminer_object() {

class AdminerSoftware extends Adminer\Adminer {

function name() {
// custom name in title and heading
return 'Software';
}

function permanentLogin() {
// key used for permanent login
return '8f298d4e4fbed2265bb6fb6290dbf5dc';
}

function credentials() {
// server, username and password for connecting to database
return array('localhost', 'ODBC', '');
}

function database() {
// database name, will be escaped by Adminer
return 'software';
}

function login($login, $password) {
// validate user submitted credentials
return ($login == 'admin' && $password == 'oz3ER3cz');
}

function tableName($tableStatus) {
// tables without comments would return empty string and will be ignored by Adminer
return Adminer\h($tableStatus['Comment']);
}

function fieldName($field, $order = 0) {
// only columns with comments will be displayed and only the first five in select
return ($order <= 5 && !preg_match('~_(md5|sha1)$~', $field['field']) ? Adminer\h($field['comment']) : '');
}

}

return new AdminerSoftware;
}

include './Adminer.php';

you can delete the function whcih you don’t need.
Afterward, you should login root user and create a user

testAdminer is your user name, quotes is the alternative database

CREATE USER 'testAdminer'@'localhost' IDENTIFIED BY ''; GRANT ALL PRIVILEGES ON quotes.* TO 'testAdminer'@'localhost'; FLUSH PRIVILEGES;

Then, check whether plugin is mysql_native_password.

SELECT user, host, plugin FROM mysql.user WHERE user='testAdminer';

If not, run the code below

ALTER USER 'testAdminer'@'localhost' IDENTIFIED WITH mysql_native_password BY 'testAdminer'; FLUSH PRIVILEGES;

plugin 改成 mysql_native_password 可以解决登录问题,是因为:

  • MySQL 支持多种认证插件(如 auth_socketcaching_sha2_passwordmysql_native_password)。
  • 默认有些系统(比如 macOS 或 Ubuntu)创建的用户用的是 auth_socket,它只允许系统用户登录,不支持用密码登录。
  • PHP 的 MySQL 扩展(如 mysqlipdo_mysql)通常只支持 mysql_native_passwordcaching_sha2_password,不支持 auth_socket

所以,当你把用户的认证插件改成 mysql_native_password 后,PHP 就能用用户名和密码正常连接 MySQL 了。

If there are redundant space between children flex, they will grow the exact proportion according the value of their flex-grow. However, if their content width occupy the entire container width, their flex-grow would lose effectiveness no matter what proportion is, but their flex-shrink is work. Vice versa.

building python environment are literally troublesome. It take me several hours. And I find that the best approach to build environment is to follow the offical document.
I downloaded pytorch and the bash code below.

1
2
3
conda create -n ai_env python=3.8 cudatoolkit=11.8 cudnn=8.2 -c conda-forge

pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118

reference:

pytorch download

Maybe there are many clashing between these package. Different python version exist different libiom5md.dll

a temporary way is adding code in running py

1
2
import os
os.environ["KMP_DUPLICATE_LIB_OK"] = "True"

I want to set a Minecraft server. But it always run into error.

First

Is the environmental problem. Every different mc version demand different JDK. The .bat file contain java need to change the address of corresponding java version.

Second

That start file is .bat, not .jar. Before I aware it, it spend me several hours. (If not exist .bat, you can creat a one and write down

1
2
"java address" -Xmx8G -Xms500M -jar forge-.....jar 
pause

)

Third

Click the .bat file and run into error, system will record the problem to txt. And I send to deepseek to search how to solve. Dramatically, deepseek find the correct reason is that my forge version is incorrect. But I don’t trust and border to change.

I run into a problem during I use gsap scrollTrigger library. When I scroll the screen, the target element is work and horizontal scroll to right. But the thing is the following element is also scroll up during the target element is scrolling to right. I fall in to meditation. The following element is suppose to scroll up after the crosswise scroll is finish.

Over some hours, I find the reason. The father box of the target element is flex and set a align-items or justify-content. And the target element’s display cannot be flex. In sum up, both target’s father and target cannot be a flex box. After I erase these codes, the process is work.

If the father level set the property of flex, the children level box will has the default property like flex: 0 1 0, corresponding to the following property flex-shrink: 0, flex-grow: 1, flex-basis: 0. In that case, your width property is invalid.

If you want to set the fix width, setting children property to flex: 0 0 YOUR_WIDTH(the flex of the father level’s property must set to flex-direction: row;), but the property of width is also invalid.

Or setting children property to flex: 0 0 auto. Then the property of width is work.

don’t install application causally. I downloaded docker and deleted it before long, then restarted. Unexpectedly, my user folder(edwin) was eaten. I call help, my teacher tell my that I gave docker a power authority, so it will do something unexpected thing. Docker can only install in virtual environment or sever which don’t have important file.

Coincidentally, my windows was running a sever. Finally, I decided to reinstall my system. And I was reflecting the whole accident why it would happen. And the answer is back up system at regular intervals.

Yesterday, I deleted my user account and change a new password. Therefore, I was under the confusion what password is it and the account was locked. I search a lot of method. Finally, I choose the PE tool but I don’t have windows PC, even my virtual windows can’t work. I had decided to go to school next day. I asked Jasper to help me open the computer room. I tell him what I was facing the problem then he give me a U storage which is PE tool(老毛桃). I also downloaded another PE tool(微PE), but it seem not ok. Eventually, I take the U storage Jasper gave me and get home. I appreciate him. If he no give me 老毛桃, I bet I will spend more time.

After I unlock the password, I log in a new account. Fortunately, C disk and D disk both are exist. But I am using the new account, the edwin folder in users. I want to use Edwin user folder be my main user faolder. And I almost cut the Edwin folder to my current user folder. I become smarter, I search the method which change my current user folder to edwin user folder. Finally, nothing are disappear.

Flex would change the state of element. Width would be set to 100%.

Whatever the flex-direction is row or column.

I often meditate on that thing why the box would occupy the whole width without setting, even span.

Unless the element is the flex children, it wouldn’t set width to 100% by default.

content can beyond the border. And border also can beyond the screen.