Žádný typy, pouze inty

This commit is contained in:
Tomáš Sláma 2025-02-11 21:50:20 +01:00
parent f8d03c6162
commit 6ffbfb0d1e
3 changed files with 101 additions and 119 deletions
asteracer-python
asteracer-rust/src

View file

@ -1,3 +1,4 @@
"""The Asteracer game implementation. Includes the base movement code + couple of QOL additions (eg. save states)."""
from __future__ import annotations
import dataclasses
@ -5,14 +6,6 @@ import random
from collections import defaultdict
from dataclasses import dataclass
from math import isqrt
from typing import List, Union, Tuple, Dict
# tohle dříve byly numpy typy, ale asi je lepší,
# ať účastníci nemusí nic instalovat...
InstType = int # np.int8
PosType = int # np.int64
SpeedType = int # np.int64
SizeType = int # np.int64
class TickFlag:
@ -23,18 +16,18 @@ class TickFlag:
@dataclass
class Racer:
x: PosType = 0
y: PosType = 0
vx: SpeedType = 0
vy: SpeedType = 0
radius: SizeType = 1
x: int = 0
y: int = 0
vx: int = 0
vy: int = 0
radius: int = 1
@dataclass(frozen=True)
class Asteroid:
x: PosType = 0
y: PosType = 0
radius: SizeType = 1
x: int = 0
y: int = 0
radius: int = 1
Goal = Asteroid
@ -42,7 +35,7 @@ Goal = Asteroid
class Instruction:
MAX_ACCELERATION = 127
def __init__(self, vx: Union[int, float] = 0, vy: Union[int, float] = 0):
def __init__(self, vx: int | float = 0, vy: int | float = 0):
"""Whatever values we get, normalize them."""
if distance_squared(vx, vy) > Instruction.MAX_ACCELERATION ** 2:
@ -62,8 +55,8 @@ class Instruction:
assert distance_squared(vx, vy) <= Instruction.MAX_ACCELERATION ** 2
self.vx = InstType(vx)
self.vy = InstType(vy)
self.vx = vx
self.vy = vy
def __hash__(self):
return hash((self.vx, self.vy))
@ -90,26 +83,26 @@ class Instruction:
@dataclass
class BoundingBox:
min_x: PosType
min_y: PosType
max_x: PosType
max_y: PosType
min_x: int
min_y: int
max_x: int
max_y: int
def width(self) -> SizeType:
return SizeType(self.max_x - self.min_x)
def width(self) -> int:
return int(self.max_x - self.min_x)
def height(self) -> SizeType:
return SizeType(self.max_y - self.min_y)
def height(self) -> int:
return int(self.max_y - self.min_y)
def distance_squared(x1, y1, x2=0, y2=0) -> PosType:
def distance_squared(x1, y1, x2=0, y2=0) -> int:
"""Squared Euclidean distance between two points."""
return (PosType(x1) - PosType(x2)) ** 2 + (PosType(y1) - PosType(y2)) ** 2
return (int(x1) - int(x2)) ** 2 + (int(y1) - int(y2)) ** 2
def euclidean_distance(x1, y1, x2=0, y2=0):
"""Integer Euclidean distance between two points. Uses integer square root."""
return PosType(isqrt(distance_squared(x1, y1, x2, y2)))
return int(isqrt(distance_squared(x1, y1, x2, y2)))
def signum(x):
@ -132,8 +125,8 @@ class Simulation:
def __init__(
self,
racer: Racer = Racer(),
asteroids: List[Asteroid] = None,
goals: List[Goal] = None,
asteroids: list[Asteroid] = None,
goals: list[Goal] = None,
bounding_box: BoundingBox = None,
):
# the initial racer state (used when resetting the simulation)
@ -146,7 +139,7 @@ class Simulation:
# to speed up the computation, we divide the bounding box (if we have one) into a grid
# we do this so we don't need to check all asteroids at each tick, only those that could collide with the racer
self._grid: Dict[Tuple[int, int], List[Asteroid]] = defaultdict(list)
self._grid: dict[tuple[int, int], list[Asteroid]] = defaultdict(list)
for asteroid in asteroids:
min_x, min_y = self._coordinate_to_grid(
@ -163,12 +156,12 @@ class Simulation:
for grid_y in range(min_y, max_y + 1):
self._grid[(grid_x, grid_y)].append(asteroid)
self.reached_goals: List[bool] = [False] * len(self.goals)
self.reached_goals: list[bool] = [False] * len(self.goals)
# a list of simulation states that can be popped (restored to)
self._pushed_states = []
def _coordinate_to_grid(self, x: float, y: float) -> Tuple[int, int]:
def _coordinate_to_grid(self, x: float, y: float) -> tuple[int, int]:
"""Translate an (x,y) coordinate into a coordinate of the grid."""
return (x // self.CELL_SIZE, y // self.CELL_SIZE)
@ -181,14 +174,14 @@ class Simulation:
self.racer.vy = division(self.racer.vy * self.DRAG_FRACTION[0], self.DRAG_FRACTION[1])
# velocity
self.racer.vx += SpeedType(vx)
self.racer.vy += SpeedType(vy)
self.racer.vx += int(vx)
self.racer.vy += int(vy)
# movement
self.racer.x += self.racer.vx
self.racer.y += self.racer.vy
def _push_out(self, obj: Union[Asteroid, BoundingBox]) -> bool:
def _push_out(self, obj: Asteroid | BoundingBox) -> bool:
"""Attempt to push the racer out of the object (if he's colliding), adjusting
his velocity accordingly (based on the angle of collision). Returns True if the
racer was pushed out, otherwise returns False."""
@ -292,7 +285,7 @@ class Simulation:
return (TickFlag.COLLIDED if collided else 0) | (TickFlag.GOAL_REACHED if goal else 0)
def simulate(self, instructions: List[Instruction]):
def simulate(self, instructions: list[Instruction]):
"""Simulate a number of instructions for the simulation (from the start)."""
self.restart()
@ -337,10 +330,10 @@ class Simulation:
lines = f.read().splitlines()
racer_parts = lines[0].split()
racer = Racer(x=PosType(racer_parts[0]), y=PosType(racer_parts[1]), radius=SizeType(racer_parts[2]))
racer = Racer(x=int(racer_parts[0]), y=int(racer_parts[1]), radius=int(racer_parts[2]))
bb_parts = lines[1].split()
bb = BoundingBox(PosType(bb_parts[0]), PosType(bb_parts[1]), PosType(bb_parts[2]), PosType(bb_parts[2]))
bb = BoundingBox(int(bb_parts[0]), int(bb_parts[1]), int(bb_parts[2]), int(bb_parts[2]))
asteroid_count = int(lines[2])
@ -349,9 +342,9 @@ class Simulation:
asteroid_parts = lines[i].split()
asteroids.append(
Asteroid(
x=PosType(asteroid_parts[0]),
y=PosType(asteroid_parts[1]),
radius=SizeType(asteroid_parts[2]),
x=int(asteroid_parts[0]),
y=int(asteroid_parts[1]),
radius=int(asteroid_parts[2]),
)
)
@ -362,9 +355,9 @@ class Simulation:
goal_parts = lines[i].split()
goals.append(
Asteroid(
x=PosType(goal_parts[0]),
y=PosType(goal_parts[1]),
radius=SizeType(goal_parts[2]),
x=int(goal_parts[0]),
y=int(goal_parts[1]),
radius=int(goal_parts[2]),
)
)
@ -390,7 +383,7 @@ class Simulation:
self.reached_goals = list(self._pushed_states[-1][1])
def save_instructions(path: str, instructions: List[Instruction]):
def save_instructions(path: str, instructions: list[Instruction]):
"""Save a list of instructions to a file:
| 4 // number if instructions
| -16 -127 // instructions...
@ -405,13 +398,13 @@ def save_instructions(path: str, instructions: List[Instruction]):
f.write(f"{instruction.vx} {instruction.vy}\n")
def load_instructions(path: str) -> List[Instruction]:
def load_instructions(path: str) -> list[Instruction]:
"""Load a list of instructions from a file (see save_instructions for the format description)."""
instructions = []
with open(path) as f:
for line in f.read().splitlines()[1:]:
instruction_parts = list(map(InstType, line.split()))
instruction_parts = list(map(int, line.split()))
instructions.append(Instruction(*instruction_parts))
return instructions

View file

@ -12,61 +12,50 @@ pub mod TickFlag {
pub type TickResult = usize;
pub type InstType = i8;
pub type PosType = i64;
pub type SpeedType = i64;
pub type SizeType = i64;
pub static MAX_ACCELERATION: isize = 127;
pub static MAX_ACCELERATION: InstType = 127;
pub static DRAG_FRACTION: (SpeedType, SpeedType) = (9, 10);
pub static COLLISION_FRACTION: (SpeedType, SpeedType) = (1, 2);
pub static DRAG_FRACTION: (isize, isize) = (9, 10);
pub static COLLISION_FRACTION: (isize, isize) = (1, 2);
pub static MAX_COLLISION_RESOLUTIONS: usize = 5;
pub static CELL_SIZE: PosType = 10_000;
pub static CELL_SIZE: isize = 10_000;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Racer {
pub x: PosType,
pub y: PosType,
pub vx: SpeedType,
pub vy: SpeedType,
pub radius: SizeType,
pub x: isize,
pub y: isize,
pub vx: isize,
pub vy: isize,
pub radius: isize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Asteroid {
pub x: PosType,
pub y: PosType,
pub radius: SizeType,
pub x: isize,
pub y: isize,
pub radius: isize,
}
pub type Goal = Asteroid;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct Instruction {
pub vx: InstType,
pub vy: InstType,
pub vx: isize,
pub vy: isize,
}
impl Instruction {
fn valid(vx: PosType, vy: PosType) -> bool {
distance_squared(vx, vy, 0, 0) <= (MAX_ACCELERATION as PosType).pow(2)
fn valid(vx: isize, vy: isize) -> bool {
distance_squared(vx, vy, 0, 0) <= (MAX_ACCELERATION).pow(2)
}
pub fn new<T>(vx: T, vy: T) -> Self
where
T: Copy + Into<PosType>,
{
let vx: i64 = vx.into();
let vy: i64 = vy.into();
pub fn new(vx: isize, vy: isize) -> Self {
if !Self::valid(vx, vy) {
// use float to properly normalize here
let float_distance = ((vx as f64).powf(2.) + (vy as f64).powf(2.)).powf(1. / 2.);
let mut vx = ((vx as f64 / float_distance) * MAX_ACCELERATION as f64) as PosType;
let mut vy = ((vy as f64 / float_distance) * MAX_ACCELERATION as f64) as PosType;
let mut vx = ((vx as f64 / float_distance) * MAX_ACCELERATION as f64) as isize;
let mut vy = ((vy as f64 / float_distance) * MAX_ACCELERATION as f64) as isize;
// if we're still over, decrement both values
if !Self::valid(vx, vy) {
@ -74,14 +63,14 @@ impl Instruction {
vy -= vy.signum();
}
return Self { vx: vx as InstType, vy: vy as InstType };
return Self { vx, vy };
}
assert!(Self::valid(vx, vy));
Self {
vx: vx as InstType,
vy: vy as InstType,
vx,
vy,
}
}
@ -89,8 +78,8 @@ impl Instruction {
let mut rng = rand::rng();
Self {
vx: rng.random::<InstType>(),
vy: rng.random::<InstType>(),
vx: rng.random::<i64>() as isize,
vy: rng.random::<i64>() as isize,
}
}
@ -110,8 +99,8 @@ impl Instruction {
.collect::<Vec<&str>>();
instructions.push(Instruction {
vx: parts[0].parse::<InstType>().unwrap(),
vy: parts[1].parse::<InstType>().unwrap(),
vx: parts[0].parse::<isize>().unwrap(),
vy: parts[1].parse::<isize>().unwrap(),
})
}
@ -130,24 +119,24 @@ impl Instruction {
#[derive(Debug, Clone, Copy)]
pub struct BoundingBox {
pub min_x: SizeType,
pub min_y: SizeType,
pub max_x: SizeType,
pub max_y: SizeType,
pub min_x: isize,
pub min_y: isize,
pub max_x: isize,
pub max_y: isize,
}
impl BoundingBox {
pub fn width(&self) -> SizeType {
pub fn width(&self) -> isize {
self.max_x - self.min_x
}
pub fn height(&self) -> SizeType {
pub fn height(&self) -> isize {
self.max_y - self.min_y
}
}
/// Squared Euclidean distance; useful for distance checks.
fn distance_squared(x1: PosType, y1: PosType, x2: PosType, y2: PosType) -> PosType {
fn distance_squared(x1: isize, y1: isize, x2: isize, y2: isize) -> isize {
(x1 - x2).pow(2) + (y1 - y2).pow(2)
}
@ -155,8 +144,8 @@ fn distance_squared(x1: PosType, y1: PosType, x2: PosType, y2: PosType) -> PosTy
///
/// Note: this implementation might break for larger position values, but since
/// the maps are never going to be this large, I'm not fixing it now.
pub fn euclidean_distance(x1: PosType, y1: PosType, x2: PosType, y2: PosType) -> PosType {
(distance_squared(x1, y1, x2, y2) as f64).sqrt() as PosType
pub fn euclidean_distance(x1: isize, y1: isize, x2: isize, y2: isize) -> isize {
(distance_squared(x1, y1, x2, y2) as f64).sqrt() as isize
}
#[derive(Debug, Clone)]
@ -170,8 +159,8 @@ pub struct Simulation {
pub reached_goals: Vec<bool>,
_grid: HashMap<(PosType, PosType), Vec<Asteroid>>,
_cell_size: PosType,
_grid: HashMap<(isize, isize), Vec<Asteroid>>,
_cell_size: isize,
}
///
@ -234,7 +223,7 @@ impl Simulation {
simulation
}
fn coordinate_to_grid(&self, x: PosType, y: PosType) -> (PosType, PosType) {
fn coordinate_to_grid(&self, x: isize, y: isize) -> (isize, isize) {
(x / self._cell_size, y / self._cell_size)
}
@ -242,11 +231,11 @@ impl Simulation {
self.racer.vx = (self.racer.vx * DRAG_FRACTION.0) / DRAG_FRACTION.1;
self.racer.vy = (self.racer.vy * DRAG_FRACTION.0) / DRAG_FRACTION.1;
self.racer.vx += instruction.vx as SpeedType;
self.racer.vy += instruction.vy as SpeedType;
self.racer.vx += instruction.vx;
self.racer.vy += instruction.vy;
self.racer.x += self.racer.vx as PosType;
self.racer.y += self.racer.vy as PosType;
self.racer.x += self.racer.vx as isize;
self.racer.y += self.racer.vy as isize;
}
fn push_from_asteroids(&mut self) -> bool {
@ -413,9 +402,9 @@ impl Simulation {
let racer_parts = parts_fn();
let racer = Racer {
x: racer_parts[0].parse::<PosType>().unwrap(),
y: racer_parts[1].parse::<PosType>().unwrap(),
radius: racer_parts[2].parse::<SizeType>().unwrap(),
x: racer_parts[0].parse::<isize>().unwrap(),
y: racer_parts[1].parse::<isize>().unwrap(),
radius: racer_parts[2].parse::<isize>().unwrap(),
vx: 0,
vy: 0,
};
@ -423,10 +412,10 @@ impl Simulation {
let bb_parts = parts_fn();
let bbox = BoundingBox {
min_x: bb_parts[0].parse::<SizeType>().unwrap(),
min_y: bb_parts[1].parse::<SizeType>().unwrap(),
max_x: bb_parts[2].parse::<SizeType>().unwrap(),
max_y: bb_parts[3].parse::<SizeType>().unwrap(),
min_x: bb_parts[0].parse::<isize>().unwrap(),
min_y: bb_parts[1].parse::<isize>().unwrap(),
max_x: bb_parts[2].parse::<isize>().unwrap(),
max_y: bb_parts[3].parse::<isize>().unwrap(),
};
let asteroid_count = parts_fn()[0].parse::<usize>().unwrap();
@ -436,9 +425,9 @@ impl Simulation {
let asteroid_parts = parts_fn();
asteroids.push(Asteroid {
x: asteroid_parts[0].parse::<PosType>().unwrap(),
y: asteroid_parts[1].parse::<PosType>().unwrap(),
radius: asteroid_parts[2].parse::<PosType>().unwrap(),
x: asteroid_parts[0].parse::<isize>().unwrap(),
y: asteroid_parts[1].parse::<isize>().unwrap(),
radius: asteroid_parts[2].parse::<isize>().unwrap(),
});
}
@ -449,9 +438,9 @@ impl Simulation {
let goal_parts = parts_fn();
goals.push(Asteroid {
x: goal_parts[0].parse::<PosType>().unwrap(),
y: goal_parts[1].parse::<PosType>().unwrap(),
radius: goal_parts[2].parse::<PosType>().unwrap(),
x: goal_parts[0].parse::<isize>().unwrap(),
y: goal_parts[1].parse::<isize>().unwrap(),
radius: goal_parts[2].parse::<isize>().unwrap(),
});
}

View file

@ -7,7 +7,7 @@ use std::path::PathBuf;
mod asteracer;
pub fn load_asteroid_graph(path: &PathBuf) -> (
Vec<(PosType, PosType)>,
Vec<(isize, isize)>,
Vec<(usize, usize)>,
Vec<(char, usize)>,
) {
@ -37,7 +37,7 @@ pub fn load_asteroid_graph(path: &PathBuf) -> (
// Load vertices
for i in 0..(n_racer + n_asteroid + n_goal) {
let line: Vec<i64> = iter
let line: Vec<isize> = iter
.next()
.unwrap()
.split_whitespace()
@ -120,7 +120,7 @@ fn main() {
// posbíráme zbývající cíle tak, že k nim poletíme přímou čarou
while simulation.reached_goals.iter().any(|&reached| !reached) {
let mut nearest_goal = None;
let mut nearest_goal_distance = PosType::MAX;
let mut nearest_goal_distance = isize::MAX;
for (i, &reached) in simulation.reached_goals.iter().enumerate() {
if !reached {