ass3: handin
This commit is contained in:
5
assignment3/Makefile
Normal file
5
assignment3/Makefile
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
all: report.pdf handin.zip
|
||||||
|
report.pdf: report.md
|
||||||
|
pandoc $< -o $@ --pdf-engine typst
|
||||||
|
handin.zip: *.py report.pdf
|
||||||
|
zip $@ $^
|
||||||
BIN
assignment3/handin.zip
Normal file
BIN
assignment3/handin.zip
Normal file
Binary file not shown.
212
assignment3/report.md
Normal file
212
assignment3/report.md
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
---
|
||||||
|
title: assignment 3
|
||||||
|
date: 2025-10-21
|
||||||
|
author: Fredrik Robertsen, Erlend Ulvund Skaarberg
|
||||||
|
---
|
||||||
|
|
||||||
|
## (a) code output
|
||||||
|
|
||||||
|
### halving game output
|
||||||
|
|
||||||
|
```
|
||||||
|
The number is 5 and it is P1's turn
|
||||||
|
P1's action: --
|
||||||
|
The number is 4 and it is P2's turn
|
||||||
|
P2's action: --
|
||||||
|
The number is 3 and it is P1's turn
|
||||||
|
P1's action: /2
|
||||||
|
The number is 1 and it is P2's turn
|
||||||
|
P2's action: --
|
||||||
|
The number is 0 and P1 won
|
||||||
|
```
|
||||||
|
|
||||||
|
this is as expected (provided by the example code)
|
||||||
|
|
||||||
|
### bucket game output
|
||||||
|
|
||||||
|
```
|
||||||
|
The state is (0, ['A', 'B', 'C']) and it is P1's turn
|
||||||
|
P1's action: B
|
||||||
|
The state is (1, [3, 1]) and it is P2's turn
|
||||||
|
P2's action: 1
|
||||||
|
The state is (0, [1]) and P1's utility is 1
|
||||||
|
```
|
||||||
|
|
||||||
|
the results follow the expected run-through of the minimax algorithm
|
||||||
|
|
||||||
|
### tic tac toe output
|
||||||
|
|
||||||
|
```
|
||||||
|
| |
|
||||||
|
---+---+---
|
||||||
|
| |
|
||||||
|
---+---+---
|
||||||
|
| |
|
||||||
|
|
||||||
|
It is P1's turn to move
|
||||||
|
P1's action: (0, 0)
|
||||||
|
|
||||||
|
x | |
|
||||||
|
---+---+---
|
||||||
|
| |
|
||||||
|
---+---+---
|
||||||
|
| |
|
||||||
|
|
||||||
|
It is P2's turn to move
|
||||||
|
P2's action: (0, 1)
|
||||||
|
|
||||||
|
x | o |
|
||||||
|
---+---+---
|
||||||
|
| |
|
||||||
|
---+---+---
|
||||||
|
| |
|
||||||
|
|
||||||
|
It is P1's turn to move
|
||||||
|
P1's action: (0, 2)
|
||||||
|
|
||||||
|
x | o | x
|
||||||
|
---+---+---
|
||||||
|
| |
|
||||||
|
---+---+---
|
||||||
|
| |
|
||||||
|
|
||||||
|
It is P2's turn to move
|
||||||
|
P2's action: (1, 1)
|
||||||
|
|
||||||
|
x | o | x
|
||||||
|
---+---+---
|
||||||
|
| o |
|
||||||
|
---+---+---
|
||||||
|
| |
|
||||||
|
|
||||||
|
It is P1's turn to move
|
||||||
|
P1's action: (1, 0)
|
||||||
|
|
||||||
|
x | o | x
|
||||||
|
---+---+---
|
||||||
|
x | o |
|
||||||
|
---+---+---
|
||||||
|
| |
|
||||||
|
|
||||||
|
It is P2's turn to move
|
||||||
|
P2's action: (2, 0)
|
||||||
|
|
||||||
|
x | o | x
|
||||||
|
---+---+---
|
||||||
|
x | o |
|
||||||
|
---+---+---
|
||||||
|
o | |
|
||||||
|
|
||||||
|
It is P1's turn to move
|
||||||
|
P1's action: (2, 1)
|
||||||
|
|
||||||
|
x | o | x
|
||||||
|
---+---+---
|
||||||
|
x | o |
|
||||||
|
---+---+---
|
||||||
|
o | x |
|
||||||
|
|
||||||
|
It is P2's turn to move
|
||||||
|
P2's action: (1, 2)
|
||||||
|
|
||||||
|
x | o | x
|
||||||
|
---+---+---
|
||||||
|
x | o | o
|
||||||
|
---+---+---
|
||||||
|
o | x |
|
||||||
|
|
||||||
|
It is P1's turn to move
|
||||||
|
P1's action: (2, 2)
|
||||||
|
|
||||||
|
x | o | x
|
||||||
|
---+---+---
|
||||||
|
x | o | o
|
||||||
|
---+---+---
|
||||||
|
o | x | x
|
||||||
|
|
||||||
|
The game is a draw
|
||||||
|
```
|
||||||
|
|
||||||
|
this is an optimal game, ending in a draw
|
||||||
|
|
||||||
|
## (b) runtime improvement with alpha-beta pruning
|
||||||
|
|
||||||
|
### results of our benchmarks
|
||||||
|
|
||||||
|
- 8.7112 seconds without alpha-beta pruning
|
||||||
|
|
||||||
|
- 0.0001 seconds with alpha-beta pruning
|
||||||
|
|
||||||
|
the results are measured at the first step of the algorithms
|
||||||
|
|
||||||
|
we can see a clear speed-up from using the alpha-beta pruning
|
||||||
|
|
||||||
|
### benchmarking code
|
||||||
|
|
||||||
|
```
|
||||||
|
game = Game()
|
||||||
|
state = game.initial_state()
|
||||||
|
game.print(state)
|
||||||
|
record = False
|
||||||
|
while not game.is_terminal(state):
|
||||||
|
player = game.to_move(state)
|
||||||
|
start_time = time.time()
|
||||||
|
action = minimax_search(game, state) # The player whose turn it is is the MAX player
|
||||||
|
if not record:
|
||||||
|
print(f"Elapsed time (minimax): {time.time() - start_time:.4f} seconds")
|
||||||
|
record = True
|
||||||
|
print(f'P{player+1}\'s action: {action}')
|
||||||
|
assert action is not None
|
||||||
|
state = game.result(state, action)
|
||||||
|
game.print(state)
|
||||||
|
print()
|
||||||
|
|
||||||
|
game = Game()
|
||||||
|
state = game.initial_state()
|
||||||
|
game.print(state)
|
||||||
|
record = False
|
||||||
|
while not game.is_terminal(state):
|
||||||
|
start_time = time.time()
|
||||||
|
player = game.to_move(state)
|
||||||
|
action = alpha_beta_search(game, state) # The player whose turn it is is the MAX player
|
||||||
|
if not record:
|
||||||
|
print(f"Elapsed time (alpha-beta-pruning): {time.time() - start_time:.9f} seconds")
|
||||||
|
record = False
|
||||||
|
print(f'P{player+1}\'s action: {action}')
|
||||||
|
assert action is not None
|
||||||
|
state = game.result(state, action)
|
||||||
|
game.print(state)
|
||||||
|
print()
|
||||||
|
```
|
||||||
|
|
||||||
|
## non-mandatory
|
||||||
|
|
||||||
|
in the provided game state
|
||||||
|
|
||||||
|
```
|
||||||
|
x | o | o
|
||||||
|
---+---+---
|
||||||
|
x | |
|
||||||
|
---+---+---
|
||||||
|
| |
|
||||||
|
```
|
||||||
|
|
||||||
|
the minimax algorithm will choose the path that leads to victory, which could be
|
||||||
|
the middle point, or the lower left one. although, due to our implementation, it
|
||||||
|
will always pick the middle point because it sees it first (it is on a higher
|
||||||
|
row). this path also provides two paths to win. it is in such a sense even more
|
||||||
|
optimal than greedily choosing the lower left option, but both options lead to
|
||||||
|
a win and are as such considered equal.
|
||||||
|
|
||||||
|
to make the algorithm choose the immediate path on the lower left, we can for
|
||||||
|
example have it greedily scan the board for such an option every recursive call
|
||||||
|
so as to discover the option of a quicke win. though this may not be more
|
||||||
|
performant due to the overhead of checking for this condition all the time, but
|
||||||
|
on this rare occasion we will at least avoid performing unecessary minimax
|
||||||
|
calls.
|
||||||
|
|
||||||
|
another way to achieve this, which could also improve the performance, would be
|
||||||
|
to keep track of path lengths. this would also let it discover such an option.
|
||||||
|
|
||||||
|
yet another way would be to implement the algorithm differently, as a BFS
|
||||||
|
algorithm that checks for the winning state before expanding it.
|
||||||
BIN
assignment3/report.pdf
Normal file
BIN
assignment3/report.pdf
Normal file
Binary file not shown.
27
flake.lock
generated
Normal file
27
flake.lock
generated
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1760878510,
|
||||||
|
"narHash": "sha256-K5Osef2qexezUfs0alLvZ7nQFTGS9DL2oTVsIXsqLgs=",
|
||||||
|
"owner": "nixos",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "5e2a59a5b1a82f89f2c7e598302a9cacebb72a67",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nixos",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
||||||
24
flake.nix
Normal file
24
flake.nix
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
description = "introki";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs =
|
||||||
|
{ self, nixpkgs, ... }:
|
||||||
|
let
|
||||||
|
system = "x86_64-linux";
|
||||||
|
pkgs = import nixpkgs { inherit system; };
|
||||||
|
in
|
||||||
|
{
|
||||||
|
devShells.${system}.default = pkgs.mkShell {
|
||||||
|
buildInputs = with pkgs; [
|
||||||
|
zip
|
||||||
|
];
|
||||||
|
shellHook = ''
|
||||||
|
echo welcome!
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user