|
1 | 1 | # flake8: NOQA E501 |
2 | 2 | importast |
| 3 | +fromcopyimportdeepcopy |
3 | 4 | fromrandomimportchoice,randint |
4 | 5 | fromstringimportascii_uppercase |
| 6 | +fromtextwrapimportdedent |
5 | 7 | fromtypingimportList |
6 | 8 |
|
7 | 9 | fromcore.textimportExerciseStep,Page,MessageStep,Disallowed,VerbatimStep |
| 10 | +fromcore.utilsimportreturns_stdout |
8 | 11 |
|
9 | 12 |
|
10 | 13 | defgenerate_board(board_type): |
@@ -1304,3 +1307,269 @@ def generate_inputs(cls): |
1304 | 1307 | super_secret_number = '7' |
1305 | 1308 |
|
1306 | 1309 | """ |
| 1310 | + |
| 1311 | + |
| 1312 | +classNestedListAssignment(Page): |
| 1313 | +title="Nested List Assignment: Playing Moves on the Board" |
| 1314 | + |
| 1315 | +classmodify_list_in_function(VerbatimStep): |
| 1316 | +""" |
| 1317 | +We've seen how to get input from the user, now let's use that to actually put pieces |
| 1318 | +on the board and play the game. For starters, try out this code: |
| 1319 | +
|
| 1320 | + __copyable__ |
| 1321 | + __program_indented__ |
| 1322 | + """ |
| 1323 | + |
| 1324 | +predicted_output_choices= [ |
| 1325 | +" ", |
| 1326 | +"X", |
| 1327 | +"' '", |
| 1328 | +"'X'", |
| 1329 | +"[' ']", |
| 1330 | +"['X']", |
| 1331 | +"[' ', ' ', ' ']", |
| 1332 | +"['X', ' ', ' ']", |
| 1333 | +"[' ', 'X', ' ']", |
| 1334 | + ] |
| 1335 | + |
| 1336 | +defprogram(self): |
| 1337 | +defplay_move(board,player): |
| 1338 | +board[1]=player |
| 1339 | + |
| 1340 | +defplay_game(): |
| 1341 | +game_board= [" "," "," "] |
| 1342 | +play_move(game_board,"X") |
| 1343 | +print(game_board) |
| 1344 | + |
| 1345 | +play_game() |
| 1346 | + |
| 1347 | +classnested_assignment_two_lines(VerbatimStep): |
| 1348 | +""" |
| 1349 | +Note how calling `play_move(game_board, 'X')` actually *modifies* `game_board` directly. |
| 1350 | +The variable `board` inside the call to `play_move` and |
| 1351 | +the variable `game_board` inside the call to `play_game` point to the same list object. |
| 1352 | +There's no copying. Python Tutor is good at showing this with arrows. |
| 1353 | +
|
| 1354 | +This also means that in this case there's no need for `play_move` to return anything, |
| 1355 | +it can just modify `board` and the caller (`play_game` in this case) will see the effect. |
| 1356 | +
|
| 1357 | +However, our board is two dimensional, represented by a nested list. |
| 1358 | +So we need to assign `player` to an element of an inner list, something like this: |
| 1359 | +
|
| 1360 | + __copyable__ |
| 1361 | + __program_indented__ |
| 1362 | + """ |
| 1363 | + |
| 1364 | +defprogram(self): |
| 1365 | +defplay_move(board,player): |
| 1366 | +row=board[1] |
| 1367 | +row[0]=player |
| 1368 | + |
| 1369 | +defplay_game(): |
| 1370 | +board= [ |
| 1371 | + [" "," "," "], |
| 1372 | + [" "," "," "], |
| 1373 | + [" "," "," "], |
| 1374 | + ] |
| 1375 | +play_move(board,"X") |
| 1376 | +print(board) |
| 1377 | + |
| 1378 | +play_game() |
| 1379 | + |
| 1380 | +classnested_assignment_input(ExerciseStep): |
| 1381 | +r""" |
| 1382 | +These two lines: |
| 1383 | +
|
| 1384 | + row = board[1] |
| 1385 | + row[0] = player |
| 1386 | +
|
| 1387 | +can be combined into one: |
| 1388 | +
|
| 1389 | + board[1][0] = player |
| 1390 | +
|
| 1391 | +The two pieces of code are pretty much exactly equivalent. Python first evaluates |
| 1392 | +`board[1]` to *get* the inner list, while the `[0] = ...` sets an element of `board[1]`. |
| 1393 | +You can see the value of `board[1]` in Bird's Eye because it's an expression, |
| 1394 | +and you could actually replace it with any other expression. |
| 1395 | +
|
| 1396 | +Now you know how to set elements in nested lists, it's time to make this interactive! |
| 1397 | +Write your own version of `play_move` that takes input from the user |
| 1398 | +to determine where to play, instead of always playing at `board[1][0]`. |
| 1399 | +It should call `input()` twice, so the user can give the row and the column |
| 1400 | +as two separate numbers. Also, our users are not programmers, so they start counting from 1, |
| 1401 | +not 0. |
| 1402 | +
|
| 1403 | +For example, if the user types in these inputs: |
| 1404 | +
|
| 1405 | + 2 |
| 1406 | + 1 |
| 1407 | +
|
| 1408 | +that means they want to play a move in the second row and first column, which is the same |
| 1409 | +as our original example. |
| 1410 | +
|
| 1411 | +Here is some starting code: |
| 1412 | +
|
| 1413 | + __copyable__ |
| 1414 | + def format_board(board): |
| 1415 | + first_row = ' ' |
| 1416 | + for i in range(len(board)): |
| 1417 | + first_row += str(i + 1) |
| 1418 | + joined_rows = [first_row] |
| 1419 | + for i in range(len(board)): |
| 1420 | + joined_row = str(i + 1) + ''.join(board[i]) |
| 1421 | + joined_rows.append(joined_row) |
| 1422 | + return "\n".join(joined_rows) |
| 1423 | +
|
| 1424 | + def play_game(): |
| 1425 | + board = [ |
| 1426 | + [' ', ' ', ' '], |
| 1427 | + [' ', ' ', ' '], |
| 1428 | + [' ', ' ', ' '], |
| 1429 | + ] |
| 1430 | + print(format_board(board)) |
| 1431 | + print('\nX to play:\n') |
| 1432 | + play_move(board, 'X') |
| 1433 | + print(format_board(board)) |
| 1434 | + print('\nO to play:\n') |
| 1435 | + play_move(board, 'O') |
| 1436 | + print(format_board(board)) |
| 1437 | +
|
| 1438 | + def play_move(board, player): |
| 1439 | + ... |
| 1440 | +
|
| 1441 | + play_game() |
| 1442 | +
|
| 1443 | +This calls `play_move` twice so the user will need to enter two pairs of numbers. |
| 1444 | +Here's an example of what a 'game' should look like: |
| 1445 | +
|
| 1446 | + 123 |
| 1447 | + 1 |
| 1448 | + 2 |
| 1449 | + 3 |
| 1450 | +
|
| 1451 | + X to play: |
| 1452 | +
|
| 1453 | + 2 |
| 1454 | + 1 |
| 1455 | + 123 |
| 1456 | + 1 |
| 1457 | + 2X |
| 1458 | + 3 |
| 1459 | +
|
| 1460 | + O to play: |
| 1461 | +
|
| 1462 | + 1 |
| 1463 | + 3 |
| 1464 | + 123 |
| 1465 | + 1 O |
| 1466 | + 2X |
| 1467 | + 3 |
| 1468 | +
|
| 1469 | +You don't need to use the provided code exactly, it's just to give you a feeling of what's happening. |
| 1470 | +The important thing is that your `play_move` function modifies the `board` argument correctly. |
| 1471 | +It doesn't need to return or print anything, that will not be checked. |
| 1472 | +
|
| 1473 | +You can assume that the user will always enter valid numbers. Later we will learn how to deal |
| 1474 | +with invalid inputs, like numbers out of range or inputs that aren't numbers at all. |
| 1475 | + """ |
| 1476 | + |
| 1477 | +hints=""" |
| 1478 | +Your function needs to call `input()` twice. Input isn't passed to `play_move` as an argument. |
| 1479 | +`input()` always returns a string. |
| 1480 | +A string that looks like a number is still a string, not a number. |
| 1481 | +List indices have to be numbers, not strings. |
| 1482 | +If the board is 3x3, the user might input 1, 2, or 3 for each coordinate. |
| 1483 | +What are the valid indices of a list of length 3? |
| 1484 | +You need to take the input of 1, 2, or 3 and turn it into 0, 1, or 2. |
| 1485 | +You also need to be able to handle bigger boards, like 9x9 or beyond. |
| 1486 | +You can't do maths with strings, only numbers. |
| 1487 | +How can you convert a string to a number? |
| 1488 | +Once you've got two numbers, you need to modify the nested list `board` with them. |
| 1489 | +The code for this has been shown to you above. |
| 1490 | +You just need to use the numbers from user input instead of the hardcoded 1 and 0. |
| 1491 | +You can use nested subscripting in one line, or do it in two steps. |
| 1492 | + """ |
| 1493 | + |
| 1494 | +no_returns_stdout=True |
| 1495 | + |
| 1496 | +defsolution(self): |
| 1497 | +defplay_move(board,player): |
| 1498 | +row=int(input())-1 |
| 1499 | +col=int(input())-1 |
| 1500 | +board[row][col]=player |
| 1501 | + |
| 1502 | +returnplay_move |
| 1503 | + |
| 1504 | +@classmethod |
| 1505 | +defwrap_solution(cls,func): |
| 1506 | +@returns_stdout |
| 1507 | +defwrapper(**kwargs): |
| 1508 | +board=kwargs["board"]=deepcopy(kwargs["board"]) |
| 1509 | + |
| 1510 | +defformat_board(): |
| 1511 | +first_row=' ' |
| 1512 | +foriinrange(len(board)): |
| 1513 | +first_row+=str(i+1) |
| 1514 | +joined_rows= [first_row] |
| 1515 | +foriinrange(len(board)): |
| 1516 | +joined_row=str(i+1)+''.join(board[i]) |
| 1517 | +joined_rows.append(joined_row) |
| 1518 | +return"\n".join(joined_rows) |
| 1519 | + |
| 1520 | +func(**kwargs) |
| 1521 | +print(format_board()) |
| 1522 | +returnwrapper |
| 1523 | + |
| 1524 | +@classmethod |
| 1525 | +defgenerate_inputs(cls): |
| 1526 | +return { |
| 1527 | +"stdin_input": [str(randint(1,3)),str(randint(1,3))], |
| 1528 | +"player":choice(ascii_uppercase), |
| 1529 | +"board":generate_board(choice(["row","col","diag"])), |
| 1530 | + } |
| 1531 | + |
| 1532 | +tests= [ |
| 1533 | + ( |
| 1534 | + { |
| 1535 | +"stdin_input": ["2","1"], |
| 1536 | +"board": [ |
| 1537 | + [" "," "," "], |
| 1538 | + [" "," "," "], |
| 1539 | + [" "," "," "], |
| 1540 | + ], |
| 1541 | +"player":"X", |
| 1542 | + }, |
| 1543 | +dedent("""\ |
| 1544 | + <input: 2> |
| 1545 | + <input: 1> |
| 1546 | + 123 |
| 1547 | + 1 |
| 1548 | + 2X |
| 1549 | + 3 |
| 1550 | + """), |
| 1551 | + ), |
| 1552 | + ( |
| 1553 | + { |
| 1554 | +"stdin_input": ["1","3"], |
| 1555 | +"board": [ |
| 1556 | + [" "," "," "], |
| 1557 | + ["X"," "," "], |
| 1558 | + [" "," "," "], |
| 1559 | + ], |
| 1560 | +"player":"O", |
| 1561 | + }, |
| 1562 | +dedent("""\ |
| 1563 | + <input: 1> |
| 1564 | + <input: 3> |
| 1565 | + 123 |
| 1566 | + 1 O |
| 1567 | + 2X |
| 1568 | + 3 |
| 1569 | + """), |
| 1570 | + ), |
| 1571 | + ] |
| 1572 | + |
| 1573 | +final_text=""" |
| 1574 | +Brilliant! You're almost ready to put it all together, keep going! |
| 1575 | +""" |