Album
米Teen唔鐘意玩現成品,喜歡由零件開始砌。99次失敗唔係問題,最緊要係有1次成功!
米Teen總部設在聖公會白約翰會督中學506室,在午膳後和放學後集會。有興趣參與的同學,請與會長或導師聯絡。
2025-11-04 04:48:45
溫度24℃ 濕度74% 氣壓1015.89hPa
2025-10-31 14:59:48
閃電24km (0)
2025-10-31 14:59:48
閃電24km (0)
2025-10-31 14:59:48
閃電24km (0)
2025-10-31 14:59:47
閃電24km (0)
2025-10-31 14:35:01
閃電27km (0)
2025-10-30 17:57:03
閃電31km (0)
2025-10-30 17:17:28
閃電24km (0)
2025-10-27 17:51:30
閃電31km (0)
2025-10-25 14:35:14
閃電27km (0)
2025-10-25 14:35:12
閃電31km (0)
溫度24℃ 濕度74% 氣壓1015.89hPa
2025-10-31 14:59:48
閃電24km (0)
2025-10-31 14:59:48
閃電24km (0)
2025-10-31 14:59:48
閃電24km (0)
2025-10-31 14:59:47
閃電24km (0)
2025-10-31 14:35:01
閃電27km (0)
2025-10-30 17:57:03
閃電31km (0)
2025-10-30 17:17:28
閃電24km (0)
2025-10-27 17:51:30
閃電31km (0)
2025-10-25 14:35:14
閃電27km (0)
2025-10-25 14:35:12
閃電31km (0)
同Dobot機械臂玩過三關
>>同Dobot機械臂玩...
用Python程式控制Dobot機械臂同你玩過三關。用Python程式編寫過三關戰術,控制Dobot機械臂執放與移動棋子,透過Webcam送回的影像偵測棋盤和棋子,辨認人類對手的棋路,讓Dobot機械臂與人對弈。本製作被邀請在2019年6月的香港電訊5G科技嘉年華展出。
系統規格如下:
編程語言:Python 機械臂:Dobot Magician 影像:Webcam
開發程序
- 用Python控制Dobot移動及使用吸咀運送棋子
- 用Python編寫過三關戰術
- 用Python分析WebCam傳送回來的影像
- 用Python辨認棋盤上的棋子
- 用Python控制流程
項目負責同學:4C洪子洛
用Python程式控制Dobot機械臂搬動棋子的試驗
剛完成了玩過三關的程式,還要debug,稍後再優化程序。
Author: HUNG TSZ LOK (SKH Bishop Baker Secondary School, class 4C)
Last modified: 2019 June
| 1 | #pip install pydobot |
| 2 | import cv2 |
| 3 | import random |
| 4 | import time |
| 5 | import numpy as np |
| 6 | from pydobot import Dobot |
| 7 | |
| 8 | # connect and setup DOBOT |
| 9 | d = Dobot('COM5', 0)
|
| 10 | d._set_end_effector_suction_cup(False) |
| 11 | d._set_ptp_coordinate_params(150, 150) |
| 12 | d._set_ptp_common_params(150, 100) |
| 13 | |
| 14 | |
| 15 | print('start')
|
| 16 | movex = {
|
| 17 | '000000000': [0, 4, 2, 4, 6, 8], '100020000': [8], '001020000': [6], |
| 18 | '000020100': [2], '000020001': [0], '020010000': [0, 2], |
| 19 | '000210000': [0, 6], '000012000': [2, 8], '000010020': [6, 8], |
| 20 | '120010002': [3, 6], '021010200': [5, 8], '002210100': [7, 8], |
| 21 | '100210002': [1, 2], '001012200': [1], '200012001': [7], '200010021': [5], |
| 22 | '002010120': [3], '000020000': [0, 2, 6, 8], '200000000': [4], |
| 23 | '002000000': [4], '000000200': [4], '000000002': [4], |
| 24 | '002010200': [1, 3, 5, 7], '200010002': [1, 3, 5, 7], '000200100': [8], |
| 25 | '000000021': [2], '001002000': [0], '120000000': [6], '000200121': [2], |
| 26 | '001002021': [0], '121002000': [6], '120200100': [8], '000000120': [0], |
| 27 | '000002001': [6], '021000000': [8], '100200000': [2], '100200120': [2], |
| 28 | '000002121': [0], '021002001': [6], '121200000': [8]} |
| 29 | |
| 30 | |
| 31 | class Checkerboard: |
| 32 | def __init__( |
| 33 | self, test=0, z=-70, x=265.3, y=45.5, x_offset=-35.8, y_offset=-39, |
| 34 | home=[140, 100, 0], first=1, r_value=1500, b_value=1500, upr=0.0, |
| 35 | cx=50, cy=50): |
| 36 | self.test = test |
| 37 | self.r_value = r_value # color area threshold |
| 38 | self.b_value = b_value # '' |
| 39 | |
| 40 | self.round = 0 # moved count |
| 41 | self.first = first # is bot first |
| 42 | self.blankcount = 0 |
| 43 | self.x, self.y, self.z = x, y, z # grid #1 coordinates (top left of Dobot #1) |
| 44 | |
| 45 | # ** x, y, x_offset, y_offset must be as precise as possible for Dobot #1. |
| 46 | |
| 47 | self.upr = upr # unit pixel ratio |
| 48 | self.cx, self.cy = cx, cy # grid #1 center image coordinates (normally (55, 55)) |
| 49 | self.x_offset, self.y_offset = x_offset, y_offset |
| 50 | self.xupr, self.yupr = self.x_offset / 95, self.y_offset / 95 # unit pixel ratio |
| 51 | self.cap = cv2.VideoCapture(0) |
| 52 | self.home = home # home position |
| 53 | self.sit = 0 # situation id |
| 54 | self.pos = [(self.x, self.y+self.y_offset*3), |
| 55 | (self.x+self.x_offset, self.y+self.y_offset*3), |
| 56 | (self.x+2*x_offset, self.y+self.y_offset*3), |
| 57 | (self.x, self.y-self.y_offset), |
| 58 | (self.x+self.x_offset, self.y-self.y_offset), |
| 59 | (self.x+2*self.x_offset, self.y-self.y_offset)] # default chess coordinates |
| 60 | self.checkattemp = 0 # saving checks |
| 61 | self.checkerboard = '0' * 9 # checkerboard with color id |
| 62 | self.checkerboardchecks = ['0' * 9] * 3 # list of self.checkattemp |
| 63 | self.grid = np.array([[0, 100, 200, 300], [0, 100, 200, 300]]) # cropping |
| 64 | |
| 65 | def c(self): # capture the checkerboard |
| 66 | d._set_ptp_cmd(self.home[0], self.home[1], self.home[2], 0, 0x02) |
| 67 | self.frame = self.cap.read()[1] |
| 68 | |
| 69 | def com(self): # compare new and old checkerboard |
| 70 | if (sum(map(lambda c0, c1, c2: c0 == c1 == c2, |
| 71 | self.checkerboardchecks[0], self.checkerboardchecks[1], |
| 72 | self.checkerboardchecks[2])) == 9): # 3 same check results of new |
| 73 | different = sum(map(lambda c, c0: c != c0, self.checkerboard, |
| 74 | self.checkerboardchecks[0])) # difference of old and new |
| 75 | if different == 1: |
| 76 | if ( |
| 77 | sum([not int(x) for x in self.checkerboard if x == '0'] |
| 78 | ) - sum([not int(x) for x in self.checkerboardchecks[0] |
| 79 | if x == '0']) == 1): # grid with chess different count = -1 |
| 80 | return True |
| 81 | else: |
| 82 | return False |
| 83 | elif different == 0: |
| 84 | return False |
| 85 | else: |
| 86 | pass |
| 87 | else: |
| 88 | return False |
| 89 | |
| 90 | def cnt(self, src, lmask, umask, lmask1=0, umask1=0, getCenter=False): # find contour |
| 91 | blurred = cv2.GaussianBlur(src, (5, 5), 0) |
| 92 | hsv = cv2.cvtColor(blurred, cv2.COLOR_BGR2HSV) |
| 93 | |
| 94 | mask = cv2.inRange(hsv, lmask, umask) |
| 95 | if lmask1 + umask1 is not 0: |
| 96 | mask1 = cv2.inRange(hsv, lmask1, umask1) |
| 97 | mask = mask + mask1 |
| 98 | contours = cv2.findContours(mask, cv2.RETR_EXTERNAL, |
| 99 | cv2.CHAIN_APPROX_NONE)[1] |
| 100 | if len(contours) > 0: |
| 101 | contour = max(contours, key=cv2.contourArea) # only return the contour with max area |
| 102 | area = cv2.contourArea(contour) |
| 103 | if getCenter: |
| 104 | Moments = cv2.moments(contour) |
| 105 | if Moments['m00'] != 0: |
| 106 | cX = int(Moments['m10'] / Moments['m00']) |
| 107 | cY = int(Moments['m01'] / Moments['m00']) |
| 108 | else: |
| 109 | cX, cY = (self.cx, self.cy) |
| 110 | return contour, area, (cX, cY) |
| 111 | else: |
| 112 | return contour, area |
| 113 | else: |
| 114 | if getCenter: |
| 115 | return 0, 0, (False, False) |
| 116 | else: |
| 117 | return 0, 0 |
| 118 | |
| 119 | def cntcenter(self, src, threshold, lmask, umask, lmask1=0, umask1=0): # find contour of all placed chess and get their centers |
| 120 | blurred = cv2.GaussianBlur(src, (5, 5), 0) |
| 121 | hsv = cv2.cvtColor(blurred, cv2.COLOR_BGR2HSV) |
| 122 | |
| 123 | mask = cv2.inRange(hsv, lmask, umask) |
| 124 | if lmask1 + umask1 is not 0: |
| 125 | mask1 = cv2.inRange(hsv, lmask1, umask1) |
| 126 | mask = mask + mask1 |
| 127 | contours = cv2.findContours(mask, cv2.RETR_EXTERNAL, |
| 128 | cv2.CHAIN_APPROX_NONE)[1] |
| 129 | |
| 130 | self.centers = [] |
| 131 | if len(contours) > 0: |
| 132 | for contour in contours: |
| 133 | if cv2.contourArea(contour) < threshold: |
| 134 | continue |
| 135 | else: |
| 136 | Moments = cv2.moments(contour) |
| 137 | cX = int(Moments['m10'] / Moments['m00']) |
| 138 | cY = int(Moments['m01'] / Moments['m00']) |
| 139 | center = (cX, cY) |
| 140 | self.centers.append(center) |
| 141 | return self.centers |
| 142 | |
| 143 | def transform(self): # matrix perspective transform of image |
| 144 | global initstat, pts1 |
| 145 | contour, area = self.cnt(self.frame, np.array([0, 0, 0]), |
| 146 | np.array([255, 150, 75])) |
| 147 | print('board area = ', area)
|
| 148 | if area < 10000: |
| 149 | return True |
| 150 | pass |
| 151 | |
| 152 | epsilon = 0.05*cv2.arcLength(contour, True) |
| 153 | approx = cv2.approxPolyDP(contour, epsilon, True) |
| 154 | |
| 155 | if initstat or True: |
| 156 | pts1 = np.float32(approx) |
| 157 | initstat = False |
| 158 | pts2 = np.float32([[0, 0], [0, 300], [300, 300], [300, 0]]) |
| 159 | M = cv2.getPerspectiveTransform(pts1, pts2) |
| 160 | self.frame = cv2.warpPerspective(self.frame, M, (300, 300)) |
| 161 | |
| 162 | self.grids = [] |
| 163 | for i in range(9): |
| 164 | self.grids.append( |
| 165 | self.frame[self.grid[0, i // 3]:self.grid[0, i // 3+1], |
| 166 | self.grid[1, i % 3]:self.grid[1, i % 3+1]]) |
| 167 | |
| 168 | def check(self, delay=0, startc=0, getCenter=0): # recognize the checkerboard |
| 169 | global initstat |
| 170 | self.c() |
| 171 | try: |
| 172 | if self.transform(): |
| 173 | return False |
| 174 | except cv2.error: |
| 175 | initstat = True |
| 176 | print('camera blocked')
|
| 177 | return False |
| 178 | |
| 179 | '''if startc: |
| 180 | return np.std(self.frame, 2).std() < 12''' |
| 181 | self.centers = {}
|
| 182 | self.checkattemp += 1 |
| 183 | checking = '' |
| 184 | print('red area blue area')
|
| 185 | for gn, i in zip(self.grids, range(9)): |
| 186 | # blue |
| 187 | lower_blue = np.array([90, 100, 90]) |
| 188 | upper_blue = np.array([110, 255, 255]) |
| 189 | # red |
| 190 | lower_red = np.array([0, 100, 90]) |
| 191 | upper_red = np.array([10, 255, 255]) |
| 192 | lower_red1 = np.array([170, 100, 90]) |
| 193 | upper_red1 = np.array([180, 255, 255]) |
| 194 | |
| 195 | if getCenter: |
| 196 | self.blue_centers = self.cntcenter( |
| 197 | self.frame, self.b_value, lower_blue, upper_blue) |
| 198 | |
| 199 | self.red_centers = self.cntcenter( |
| 200 | self.frame, self.r_value, lower_red, upper_red, lower_red1, upper_red1,) |
| 201 | |
| 202 | print(self.blue_centers, '\n', self.red_centers) |
| 203 | return self.blue_centers, self.red_centers |
| 204 | else: |
| 205 | contour_blue, area_blue = self.cnt(gn, lower_blue, upper_blue) |
| 206 | contour_red, area_red = self.cnt( |
| 207 | gn, lower_red, upper_red, lower_red1, upper_red1) |
| 208 | print(area_red, area_blue, sep=' ') |
| 209 | |
| 210 | if area_red > self.r_value and area_blue < self.b_value: |
| 211 | checking += '1' |
| 212 | elif area_red < self.r_value and area_blue > self.b_value: |
| 213 | checking += '2' |
| 214 | elif area_red < self.r_value and area_blue < self.b_value: |
| 215 | checking += '0' |
| 216 | else: |
| 217 | checking += '3' |
| 218 | |
| 219 | print('checking = ', checking)
|
| 220 | if startc: |
| 221 | if checking == '000000000': |
| 222 | return True |
| 223 | self.checkerboardv = np.array( |
| 224 | [int(x) for x in self.checkerboard]).reshape(3, 3) |
| 225 | if '3' in checking: |
| 226 | return False |
| 227 | if checking == '000000000' and self.first == 1 and self.round == 0: |
| 228 | self.checkerboard = checking |
| 229 | return True |
| 230 | else: |
| 231 | self.checkerboardchecks[self.checkattemp % 3] = checking |
| 232 | if checking == '000000000': |
| 233 | if self.checkerboard != '000000000': |
| 234 | self.blankcount += 1 |
| 235 | else: |
| 236 | self.blankcount = 0 |
| 237 | if self.com(): |
| 238 | self.checkerboard = checking |
| 239 | return True |
| 240 | else: |
| 241 | return False |
| 242 | |
| 243 | def rows(self): # divide the checkerboard into 8 rows |
| 244 | self.checkerboardv = np.array( |
| 245 | [int(x) for x in self.checkerboard]).reshape(3, 3) |
| 246 | self.r = list() |
| 247 | self.r.append((self.checkerboardv[0], 0)) |
| 248 | self.r.append((self.checkerboardv[1], 1)) |
| 249 | self.r.append((self.checkerboardv[2], 2)) |
| 250 | self.r.append((self.checkerboardv[:, 0], 3)) |
| 251 | self.r.append((self.checkerboardv[:, 1], 4)) |
| 252 | self.r.append((self.checkerboardv[:, 2], 5)) |
| 253 | self.r.append((self.checkerboardv.diagonal(), 6)) |
| 254 | self.r.append((np.flip(self.checkerboardv, 1).diagonal(), 7)) |
| 255 | |
| 256 | def situation(self): |
| 257 | self.sit = 0 |
| 258 | self.rows() |
| 259 | for rn in self.r: |
| 260 | if np.count_nonzero(rn[0] == 2) == 3: # lose |
| 261 | self.sit = 7 |
| 262 | return self.sit |
| 263 | elif np.count_nonzero(rn[0] == 1) == 3: # win |
| 264 | self.sit = 8 |
| 265 | return self.sit |
| 266 | if np.count_nonzero(self.checkerboardv) == 9: # draw |
| 267 | self.sit = 6 |
| 268 | return self.sit |
| 269 | |
| 270 | if self.sit == 0: |
| 271 | for rn in self.r: |
| 272 | if np.count_nonzero(rn[0] == 0): # 0xx |
| 273 | self.sit = 0 |
| 274 | self.move = [rn[1], int(np.where(rn[0] == 0)[0][0])] |
| 275 | else: |
| 276 | pass |
| 277 | |
| 278 | for rn in self.r: |
| 279 | if (np.count_nonzero( |
| 280 | rn[0] == 2) == 2 and np.count_nonzero( |
| 281 | rn[0] == 0) == 1): # 220 |
| 282 | self.sit = 1 |
| 283 | self.move = [rn[1], int(np.where(rn[0] == 0)[0][0])] |
| 284 | break |
| 285 | |
| 286 | for rn in self.r: |
| 287 | if (np.count_nonzero( |
| 288 | rn[0] == 0) == 1 and np.count_nonzero( |
| 289 | rn[0] == 1) == 2): # 110 |
| 290 | self.sit = 2 |
| 291 | self.move = [rn[1], int(np.where(rn[0] == 0)[0][0])] |
| 292 | break |
| 293 | |
| 294 | if self.checkerboard in movex: |
| 295 | self.move = random.choice(movex[self.checkerboard]) |
| 296 | self.move = [self.move // 3, self.move % 3] |
| 297 | else: |
| 298 | if self.move[0] in range(0, 3): |
| 299 | pass |
| 300 | elif self.move[0] in range(3, 6): |
| 301 | self.move = [self.move[1], self.move[0] - 3] |
| 302 | elif self.move[0] == 6: |
| 303 | self.move = [self.move[1], self.move[1]] |
| 304 | elif self.move[0] == 7: |
| 305 | self.move = [self.move[1], 2 - self.move[1]] |
| 306 | |
| 307 | return self.sit |
| 308 | |
| 309 | def intention(self): |
| 310 | self.coor = [self.x + self.x_offset * self.move[0], |
| 311 | self.y + self.y_offset * self.move[1]] |
| 312 | print(self.move, self.coor, sep=' ') |
| 313 | return self.coor |
| 314 | |
| 315 | def m(self): # move |
| 316 | # get |
| 317 | d._set_end_effector_suction_cup(False) |
| 318 | d._set_ptp_cmd(160, 0, 0, 0, 0x02) # # |
| 319 | d._set_ptp_cmd(self.pos[self.round][0], self.pos[self.round][1], |
| 320 | self.z, 0, 0x00) |
| 321 | d._set_end_effector_suction_cup(True) |
| 322 | time.sleep(1) |
| 323 | |
| 324 | # place |
| 325 | d._set_ptp_cmd(self.coor[0], self.coor[1], self.z, 0, 0x00) |
| 326 | d._set_end_effector_suction_cup(False) |
| 327 | d._set_ptp_cmd(self.coor[0], self.coor[1], self.z + 20, 0, 0x02) |
| 328 | d._set_ptp_cmd(self.home[0], self.home[1], self.home[2], 0, 0x02) |
| 329 | |
| 330 | self.checkerboard = self.checkerboard[ |
| 331 | :self.move[0] * 3 + self.move[1]] + '1' + self.checkerboard[ |
| 332 | self.move[0] * 3 + self.move[1] + 1:] |
| 333 | self.round += 1 |
| 334 | |
| 335 | def start(self): # start game setup |
| 336 | d._set_ptp_cmd(self.home[0], self.home[1], self.home[2], 0, 0x02) |
| 337 | time.sleep(3) |
| 338 | while True: |
| 339 | if self.check(0, 1): |
| 340 | print('bot first = ', self.first)
|
| 341 | return 'Game Start' |
| 342 | else: |
| 343 | print('please clear the board.')
|
| 344 | time.sleep(1.5) |
| 345 | |
| 346 | def end(self): # end game and clear checkerboard |
| 347 | if self.sit < 5: |
| 348 | # |
| 349 | pass |
| 350 | else: |
| 351 | while self.check(getCenter=True) is False: |
| 352 | pass |
| 353 | self.reds = np.where(self.checkerboardv.reshape(9) == 1)[0] |
| 354 | self.blues = np.where(self.checkerboardv.reshape(9) == 2)[0] |
| 355 | if self.sit == 9: |
| 356 | print('interrupted')
|
| 357 | |
| 358 | if self.sit == 8: |
| 359 | print('win')
|
| 360 | |
| 361 | elif self.sit == 7: |
| 362 | print('lose')
|
| 363 | |
| 364 | elif self.sit == 6: |
| 365 | print('draw')
|
| 366 | |
| 367 | d._set_ptp_cmd(160, 0, 0, 0, 0x02) # # |
| 368 | for i, j in zip(self.red_centers, range(len(self.red_centers))): # red |
| 369 | # get |
| 370 | self.coor = [self.x + (i[1] - self.cx) * self.xupr, |
| 371 | self.y + (i[0] - self.cy) * self.yupr] |
| 372 | |
| 373 | d._set_end_effector_suction_cup(False) |
| 374 | d._set_ptp_cmd(self.coor[0], self.coor[1], self.z, 0, 0x00) |
| 375 | d._set_end_effector_suction_cup(True) |
| 376 | time.sleep(1) |
| 377 | |
| 378 | # place |
| 379 | d._set_ptp_cmd(self.pos[j][0], self.pos[j][1], self.z, 0, 0x00) |
| 380 | d._set_end_effector_suction_cup(False) |
| 381 | |
| 382 | for i, j in zip(self.blue_centers, range(len(self.blue_centers))): # blue |
| 383 | # get |
| 384 | self.coor = [self.x + (i[1] - self.cx) * self.xupr, |
| 385 | self.y + (i[0] - self.cy) * self.yupr] |
| 386 | |
| 387 | d._set_end_effector_suction_cup(False) |
| 388 | d._set_ptp_cmd(self.coor[0], self.coor[1], self.z, 0, 0x00) |
| 389 | d._set_end_effector_suction_cup(True) |
| 390 | time.sleep(1) |
| 391 | |
| 392 | # place |
| 393 | d._set_ptp_cmd(self.pos[-1][0], self.pos[-1][1], self.z, 0, 0x00) |
| 394 | |
| 395 | d._set_end_effector_suction_cup(False) |
| 396 | |
| 397 | d._set_ptp_cmd(self.pos[-1][0], self.pos[-1][1], self.z + 20, 0, |
| 398 | 0x02) |
| 399 | d._set_ptp_cmd(self.home[0], self.home[1], self.home[2], 0, 0x02) |
| 400 | self.first = True if self.sit == 9 else not self.first |
| 401 | self.sit == 0 |
| 402 | self.checkerboardv, self.checkerboard = np.array( |
| 403 | [[0, 0, 0], [0, 0, 0], [0, 0, 0]]), '0' * 9 |
| 404 | |
| 405 | self.start() |
| 406 | self.checkerboard = '0' * 9 |
| 407 | self.checkerboardchecks = ['0' * 9] * 3 |
| 408 | self.checkattemp = 0 |
| 409 | self.round = 0 |
| 410 | self.blankcount = 0 |
| 411 | |
| 412 | |
| 413 | # main |
| 414 | def main(): |
| 415 | global board |
| 416 | board = Checkerboard(1) |
| 417 | board.start() |
| 418 | while True: |
| 419 | time.sleep(0.5) |
| 420 | print(board.checkerboard) |
| 421 | try: |
| 422 | if board.round % 2 != board.first or True: # turn |
| 423 | if board.check(): |
| 424 | # cv2.imshow('frame', board.frame)
|
| 425 | # board.situation() |
| 426 | # board.intention() |
| 427 | if board.situation() < 5: |
| 428 | board.intention() |
| 429 | board.m() |
| 430 | time.sleep(6) |
| 431 | elif board.sit > 5: |
| 432 | board.end() |
| 433 | time.sleep(5 + board.round * 10) |
| 434 | else: |
| 435 | pass |
| 436 | if board.situation() > 5: |
| 437 | board.end() |
| 438 | if board.blankcount >= 10: |
| 439 | board.sit = 9 |
| 440 | board.end() |
| 441 | |
| 442 | except KeyboardInterrupt: |
| 443 | return None |
| 444 | |
| 445 | |
| 446 | if __name__ == '__main__': |
| 447 | initstat = True |
| 448 | main() |
| 449 | pass |
Album
米Teen唔鐘意玩現成品,喜歡由零件開始砌。99次失敗唔係問題,最緊要係有1次成功!
米Teen總部設在聖公會白約翰會督中學506室,在午膳後和放學後集會。有興趣參與的同學,請與會長或導師聯絡。
2025-11-04 04:48:45
溫度24℃ 濕度74% 氣壓1015.89hPa
2025-10-31 14:59:48
閃電24km (0)
2025-10-31 14:59:48
閃電24km (0)
2025-10-31 14:59:48
閃電24km (0)
2025-10-31 14:59:47
閃電24km (0)
2025-10-31 14:35:01
閃電27km (0)
2025-10-30 17:57:03
閃電31km (0)
2025-10-30 17:17:28
閃電24km (0)
2025-10-27 17:51:30
閃電31km (0)
2025-10-25 14:35:14
閃電27km (0)
2025-10-25 14:35:12
閃電31km (0)
溫度24℃ 濕度74% 氣壓1015.89hPa
2025-10-31 14:59:48
閃電24km (0)
2025-10-31 14:59:48
閃電24km (0)
2025-10-31 14:59:48
閃電24km (0)
2025-10-31 14:59:47
閃電24km (0)
2025-10-31 14:35:01
閃電27km (0)
2025-10-30 17:57:03
閃電31km (0)
2025-10-30 17:17:28
閃電24km (0)
2025-10-27 17:51:30
閃電31km (0)
2025-10-25 14:35:14
閃電27km (0)
2025-10-25 14:35:12
閃電31km (0)

絕不能在一粒細小的電阻上印上數字來表表它的阻值,只能用色條來表示。記熟了這些色碼(Resistor Color Code),就能隨時讀出一粒電阻的阻值了。
晶體管(Transistor)有很多用途,只要了解幾款晶體管的特性,已能解決大部份驅動電路(Driver circuit)的問題了。
Arduino,電子線路,網站設計等等。