Session 10 (12.01.24) - RUB-INI-Theory-of-Machine-Learning/Info1_WS23-24 GitHub Wiki
In der zehnten Session implementieren wir das Zwei-Spieler-Spiel. Hierzu erstellen wir zunächst eine Game-Klasse in der wir die bisher im globalen Namespace liegenden Funktionen zusammenfassen (insbesondere Funktionen die über Events aufgerufen werden). Außerdem müssen wir dem Renderer einen virtuellen Canvas geben, damit dieser die Spiele an unterschiedliche Positionen des Canvas zeichnen kann.
Ziele
- Game-Klasse
- Virtueller Canvas
- Zwei-Spieler-Spiel
Game-Klasse
class Game {
public:
var base_score_values = [0, 40, 100, 300, 1200];
var level_gravity = [ ... ];
var level, lines, score, soft_drop_start, gravity, current_time, spawned;
var pool, tetromino, playfield, renderer;
constructor(){
this.level = 9;
this.lines = 0;
this.score = 0;
this.soft_drop_start = 0;
this.gravity = level_gravity[this.level];
this.current_time = time();
this.spawned = false;
this.pool = TetrominoPool();
this.tetromino = Tetromino(this.pool.get(), Integer((playfield_size["x"] + 2)/2 - 2));
this.playfield = Playfield(playfield_size["x"], playfield_size["y"]);
this.renderer = Renderer(playfield_size["x"], playfield_size["y"]);
}
function step(){ ... }
function keydown(key){ ... }
function keyup(key){ ... }
}
var games = [Game()];
setEventHandler("timer", function(event) {
for var game in games do
game.step();
});
setEventHandler("canvas.keydown", function(event){
for var game in games do
game.keydown(event.key);
});
setEventHandler("canvas.keyup", function(event){
for var game in games do
game.keyup(event.key);
});
Virtueller Canvas
Übergeben der Parameter und Berechnung der Blockgröße
class Renderer {
public:
var playfield_width, playfield_height;
var x, y, width, height;
...
public:
constructor(playfield_width, playfield_height, x, y, width, height){
this.playfield_width = playfield_width;
this.playfield_height = playfield_height;
this.x = math.floor(x);
this.y = math.floor(y);
this.width = math.floor(width);
this.height = math.floor(height);
this.block_size = math.floor(math.min(
this.height/(playfield_size["y"]+2),
this.width/(playfield_size["x"]+2 + this.sidebar_size)
));
...
}
}
Aufruf im Constructor der Game-Klasse
class Game {
public:
constructor(v_canvas = [0,0,0,0]) {
if(v_canvas[2] == 0) then v_canvas[2] = canvas.width();
if(v_canvas[3] == 0) then v_canvas[3] = canvas.height();
this.renderer = Renderer(playfield_size["x"], playfield_size["y"], v_canvas[0], v_canvas[1], v_canvas[2], v_canvas[3]);
}
}
Einsetzen von Versatz und Breite/Höhe
Exemplarisch die draw_text()
Funktion:
function draw_text(text, row){
canvas.setFillColor(0,0,0);
canvas.fillRect(this.x + block_size * (this.playfield_width + 3) , this.y + row * block_size -1, 4 * block_size, 2 * block_size);
canvas.setFillColor(1,1,1);
canvas.text(this.x + block_size * (this.playfield_width + 3), this.y + row * block_size, text);
}
Weitere Anpassungen im constructor()
, in draw_next_tetromino()
und draw_block()
notwendig. Diese sind
komplett analog zu den hier exemplarisch gezeigten Einsetzungen.
Zwei-Spieler-Spiel
class Game {
public:
var incoming_lines = 0;
var outgoing_lines = 0;
...
var bindings;
constructor(v_canvas = [0,0,0,0], bindings=["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", " "]){
this.bindings = bindings;
...
}
function step() {
...
if this.incoming_lines > 0 then {
this.playfield.insert_rows(this.incoming_lines);
this.incoming_lines = 0;
}
var cleared_rows = this.playfield.remove_full_rows();
if cleared_rows > 0 then {
...
if cleared_rows < 4 then this.outgoing_lines += cleared_rows - 1;
else this.outgoing_lines += 4;
}
}
function keydown(key){
this.playfield.remove_tetromino(this.tetromino);
if key == this.bindings[4] then {
if this.soft_drop_start == 0 then this.soft_drop_start = Integer(this.tetromino.row);
this.gravity = 50;
};
if key == this.bindings[0] then this.tetromino.rotate_left();
if key == this.bindings[1] then this.tetromino.rotate_right();
if key == this.bindings[2] then this.tetromino.move_left();
if key == this.bindings[3] then this.tetromino.move_right();
if this.playfield.check_collision(this.tetromino) then {
this.tetromino.undo(); # The action caused an invalid playfield state. Undo it.
}
}
function keyup(key){
if key == this.bindings[4] and this.soft_drop_start != 0 then {
this.soft_drop_start = 0;
this.gravity = this.level_gravity[this.level];
}
}
}
var games = [
Game(v_canvas=[0, 0, canvas.width(), canvas.height()/2]),
Game(v_canvas=[0, canvas.height()/2, canvas.width(), canvas.height()/2], bindings=["8", "5", "4", "6", "0"])
];
setEventHandler("timer", function(event) {
for var game_idx in 0:games.size() do {
var game = games[game_idx];
if game.outgoing_lines > 0 then {
games[(game_idx + 1) % games.size()].incoming_lines += game.outgoing_lines;
game.outgoing_lines = 0;
}
try {
game.step();
}
catch var e do {
quitEventMode();
}
}
});