~reesmichael1/gridz

ff8b6abfe0fcf268a202059431c7a19a194cad05 — Michael Rees 5 months ago 0d8271b
Move constants to Rules struct

The new Rules struct is more flexible and handles changing certain rules
with the number of players.
4 files changed, 138 insertions(+), 98 deletions(-)

M src/city.zig
M src/game.zig
M src/player.zig
R src/{constants.zig => rules.zig}
M src/city.zig => src/city.zig +9 -8
@@ 1,9 1,9 @@
const std = @import("std");
const testing = std.testing;

const constants = @import("constants.zig");
const GameStage = @import("stage.zig").GameStage;
const Player = @import("player.zig").Player;
const Rules = @import("rules.zig").Rules;

/// A City is a location that up to three Players can settle in.
/// Cities are connected to each other as specified in the Board.


@@ 53,15 53,15 @@ pub const City = struct {
    /// Calculate the cost of building in this City
    /// (not counting connection costs).
    /// Will panic if called in a city with no room left.
    pub fn buildingCost(self: City) u8 {
    pub fn buildingCost(self: City, rules: Rules) u8 {
        if (self.firstPlayer == null) {
            return constants.first_city_cost;
            return Rules.first_city_cost;
        } else if (self.secondPlayer == null) {
            return constants.second_city_cost;
            return Rules.second_city_cost;
        }

        std.debug.assert(self.thirdPlayer == null);
        return constants.third_city_cost;
        return Rules.third_city_cost;
    }

    pub fn addPlayer(self: *City, player: Player) void {


@@ 82,25 82,26 @@ pub const City = struct {

test "building capacities in the various stages" {
    var city = City.init("Test City", 1, 2);
    const rules = Rules.init(2);

    testing.expect(city.canBuild(GameStage.Stage1));
    testing.expect(city.canBuild(GameStage.Stage2));
    testing.expect(city.canBuild(GameStage.Stage3));
    testing.expectEqual(constants.first_city_cost, city.buildingCost());
    testing.expectEqual(Rules.first_city_cost, city.buildingCost(rules));

    city.addPlayer(Player.init(testing.allocator, "Player 1"));

    testing.expect(!city.canBuild(GameStage.Stage1));
    testing.expect(city.canBuild(GameStage.Stage2));
    testing.expect(city.canBuild(GameStage.Stage3));
    testing.expectEqual(constants.second_city_cost, city.buildingCost());
    testing.expectEqual(Rules.second_city_cost, city.buildingCost(rules));

    city.addPlayer(Player.init(testing.allocator, "Player 2"));

    testing.expect(!city.canBuild(GameStage.Stage1));
    testing.expect(!city.canBuild(GameStage.Stage2));
    testing.expect(city.canBuild(GameStage.Stage3));
    testing.expectEqual(constants.third_city_cost, city.buildingCost());
    testing.expectEqual(Rules.third_city_cost, city.buildingCost(rules));

    city.addPlayer(Player.init(testing.allocator, "Player 3"));


M src/game.zig => src/game.zig +13 -11
@@ 2,7 2,6 @@ const std = @import("std");
const Allocator = std.mem.Allocator;

const auction = @import("auction.zig");
const constants = @import("constants.zig");
const gen_mod = @import("generator.zig");
const player_mod = @import("player.zig");
const input = @import("input.zig");


@@ 16,6 15,7 @@ const Loader = @import("loader.zig").Loader;
const Market = @import("resource_market.zig").Market;
const Player = player_mod.Player;
const Resource = @import("resource.zig").Resource;
const Rules = @import("rules.zig").Rules;

/// If we're talking about multiple cities, then return the string "cities."
/// Otherwise, return the string "city."


@@ 78,11 78,13 @@ pub const Game = struct {
    stage: GameStage,
    /// Whether or not the game is over.
    has_ended: bool = false,
    /// The current round of the game (starts at 1)
    /// The current round of the game (starts at 1).
    round: u16,
    /// The Rules under which the game is played.
    rules: Rules,
    /// The RNG used for all game operations.
    rng: std.rand.Xoroshiro128,
    /// The Allocator used for all allocations
    /// The Allocator used for all allocations.
    allocator: *Allocator,

    /// Prepare the Game for the first turn.


@@ 92,12 94,11 @@ pub const Game = struct {
        var rng = std.rand.DefaultPrng.init(@intCast(u64, std.time.milliTimestamp()));
        const generators = try loader.loadGenerators();
        const split = try splitGenerators(allocator, generators, &rng);

        var players = try loader.loadPlayers();
        const players = try loader.loadPlayers();

        return Game{
            .grid = try loader.loadGrid(),
            .players = try loader.loadPlayers(),
            .players = players,
            .gen_market = split[0],
            .future_gens = split[1],
            .hidden_generators = split[2],


@@ 105,6 106,7 @@ pub const Game = struct {
            .resource_market = try loader.loadResourceMarket(),
            .stage = GameStage.Stage1,
            .rng = rng,
            .rules = Rules.init(players.len),
            .round = 1,
            .allocator = allocator,
        };


@@ 447,7 449,7 @@ pub const Game = struct {
                    // from the list of players eligible to buy a generator.
                    to_remove = purchase.buyer;

                    try purchase.buyer.buyGenerator(purchase.gen, purchase.cost);
                    try purchase.buyer.buyGenerator(purchase.gen, purchase.cost, self.rules);

                    // Update the generator markets by removing the purchased generator,
                    // replacing it with a generator from the stack,


@@ 557,14 559,14 @@ pub const Game = struct {
        // Ask each utuplayer to choose how many cities they will power.
        for (self.players) |*player| {
            const cities_powered = try self.powerCities(player);
            const earned = constants.getPaymentForCities(cities_powered);
            const earned = self.rules.getPaymentForCities(cities_powered);
            try stdout.print("{}, you earned {} GZD.\n", .{ player.name, earned });
            player.money += earned;
        }

        // Refill the resource market.
        for ([_]Resource{ Resource.Coal, Resource.Oil, Resource.Garbage, Resource.Uranium }) |resource| {
            const count = constants.getResourcesToRefill(self.stage, resource);
            const count = self.rules.getResourcesToRefill(self.stage, resource);
            try self.resource_market.refillResource(resource, count);
        }



@@ 656,7 658,7 @@ pub const Game = struct {
                    continue;
                }

                const build_cost = city.buildingCost();
                const build_cost = city.buildingCost(self.rules);
                const connection_cost = try self.grid.getMinConnectionCost(city.*, player.cities);
                const total_cost = build_cost + connection_cost;



@@ 840,7 842,7 @@ pub const Game = struct {
        }

        for (self.players) |player| {
            if (player.cities.len >= constants.getStage2Trigger(self.players.len)) {
            if (player.cities.len >= self.rules.stage2_trigger) {
                self.stage = GameStage.Stage2;
                return;
            }

M src/player.zig => src/player.zig +4 -4
@@ 2,7 2,6 @@ const std = @import("std");
const testing = std.testing;
const Allocator = std.mem.Allocator;

const constants = @import("constants.zig");
const gen_mod = @import("generator.zig");
const input = @import("input.zig");
const getCitiesToShow = @import("game.zig").getCitiesToShow;


@@ 10,6 9,7 @@ const getCitiesToShow = @import("game.zig").getCitiesToShow;
const City = @import("city.zig").City;
const Generator = gen_mod.Generator;
const Resource = @import("resource.zig").Resource;
const Rules = @import("rules.zig").Rules;

/// A Player is a competitor in the game.
pub const Player = struct {


@@ 59,13 59,13 @@ pub const Player = struct {

    /// Actually perform the mechanics of buying a generator (i.e., deduct the cost
    /// from the player's cash reserves and add the generator to the player's inventory.)
    pub fn buyGenerator(self: *Player, gen: Generator, cost: u64) !void {
    pub fn buyGenerator(self: *Player, gen: Generator, cost: u64, rules: Rules) !void {
        self.money -= cost;

        var gens = std.ArrayList(Generator).init(self.allocator);
        defer gens.deinit();

        if (self.generators.len == constants.max_gens) {
        if (self.generators.len == rules.max_gens) {
            const stdout = std.io.getStdOut().outStream();
            try stdout.print("You have reached the maximum number of generators.\n", .{});
            try stdout.print("You currently own these generators:\n\n", .{});


@@ 90,7 90,7 @@ pub const Player = struct {
                    }
                }

                if (gens.items.len == constants.max_gens) {
                if (gens.items.len == rules.max_gens) {
                    try stdout.print("You do not own generator {}.\n", .{remove_ix});
                    gens.deinit();
                    gens = std.ArrayList(Generator).init(self.allocator);

R src/constants.zig => src/rules.zig +112 -75
@@ 1,86 1,123 @@
// TODO: make this module more flexible based on number of players
const std = @import("std");

const GameStage = @import("stage.zig").GameStage;
const Resource = @import("resource.zig").Resource;

pub const max_gens = 3;
/// Rules keeps track of the constants of the game,
/// some of which are set depending on the number of players.
pub const Rules = struct {
    pub const first_city_cost: u8 = 10;
    pub const second_city_cost: u8 = 15;
    pub const third_city_cost: u8 = 20;

pub const first_city_cost: u8 = 10;
pub const second_city_cost: u8 = 15;
pub const third_city_cost: u8 = 20;
    gens_removed: u8 = 8,
    game_end_gens: u8 = 17,
    max_gens: u8 = 3,
    players: usize = undefined,
    stage2_trigger: u8 = 7,

/// Get how much money should be paid to a player
/// based on how many cities they powered this round.
pub fn getPaymentForCities(cities: u8) u64 {
    switch (cities) {
        0 => return 10,
        1 => return 22,
        2 => return 33,
        3 => return 44,
        4 => return 54,
        5 => return 64,
        6 => return 73,
        7 => return 82,
        8 => return 90,
        9 => return 98,
        10 => return 105,
        11 => return 112,
        12 => return 118,
        13 => return 124,
        14 => return 128,
        15 => return 134,
        16 => return 138,
        17 => return 142,
        18 => return 145,
        19 => return 148,
        20 => return 150,
        else => unreachable,
    pub fn init(player_count: usize) Rules {
        switch (player_count) {
            2 => return Rules{
                .players = player_count,
                .max_gens = 4,
                .game_end_gens = 21,
                .stage2_trigger = 10,
            },
            3 => return Rules{
                .players = player_count,
            },
            4 => return Rules{
                .players = player_count,
                .gens_removed = 4,
            },
            5 => return Rules{
                .players = player_count,
                .gens_removed = 0,
                .game_end_gens = 15,
            },
            6 => return Rules{
                .players = player_count,
                .gens_removed = 0,
                .game_end_gens = 14,
                .stage2_trigger = 6,
            },
            else => unreachable,
        }
    }
}

// TODO: limit total resources available in the game
/// Get the number of a resource that should be put back into the market
/// depending on the current game state.
pub fn getResourcesToRefill(stage: GameStage, resource: Resource) u8 {
    switch (stage) {
        GameStage.Stage1 => {
            switch (resource) {
                Resource.Coal => return 4,
                Resource.Oil => return 2,
                Resource.Garbage => return 1,
                Resource.Uranium => return 1,
                else => unreachable,
            }
        },
        GameStage.Stage2 => {
            switch (resource) {
                Resource.Coal => return 5,
                Resource.Oil => return 3,
                Resource.Garbage => return 2,
                Resource.Uranium => return 1,
                else => unreachable,
            }
        },
        GameStage.Stage3 => {
            switch (resource) {
                Resource.Coal => return 3,
                Resource.Oil => return 4,
                Resource.Garbage => return 3,
                Resource.Uranium => return 1,
                else => unreachable,
            }
        },
    /// Get how much money should be paid to a player
    /// based on how many cities they powered this round.
    pub fn getPaymentForCities(self: Rules, cities: u8) u64 {
        switch (cities) {
            0 => return 10,
            1 => return 22,
            2 => return 33,
            3 => return 44,
            4 => return 54,
            5 => return 64,
            6 => return 73,
            7 => return 82,
            8 => return 90,
            9 => return 98,
            10 => return 105,
            11 => return 112,
            12 => return 118,
            13 => return 124,
            14 => return 128,
            15 => return 134,
            16 => return 138,
            17 => return 142,
            18 => return 145,
            19 => return 148,
            20 => return 150,
            else => unreachable,
        }
    }
}

/// Get the number of cities required in one player's network
/// to move the game into stage 2.
pub fn getStage2Trigger(players: usize) u8 {
    return switch (players) {
        2 => 10,
        3, 4, 5 => 7,
        6 => 6,
        else => unreachable,
    };
}
    // TODO: limit total resources available in the game
    /// Get the number of a resource that should be put back into the market
    /// depending on the current game state.
    pub fn getResourcesToRefill(self: Rules, stage: GameStage, resource: Resource) u8 {
        switch (stage) {
            GameStage.Stage1 => {
                switch (resource) {
                    Resource.Coal => return 4,
                    Resource.Oil => return 2,
                    Resource.Garbage => return 1,
                    Resource.Uranium => return 1,
                    else => unreachable,
                }
            },
            GameStage.Stage2 => {
                switch (resource) {
                    Resource.Coal => return 5,
                    Resource.Oil => return 3,
                    Resource.Garbage => return 2,
                    Resource.Uranium => return 1,
                    else => unreachable,
                }
            },
            GameStage.Stage3 => {
                switch (resource) {
                    Resource.Coal => return 3,
                    Resource.Oil => return 4,
                    Resource.Garbage => return 3,
                    Resource.Uranium => return 1,
                    else => unreachable,
                }
            },
        }
    }

    /// Get the number of cities required in one player's network
    /// to move the game into stage 2.
    pub fn getStage2Trigger(self: Rules) u8 {
        return switch (self.players) {
            2 => 10,
            3, 4, 5 => 7,
            6 => 6,
            else => unreachable,
        };
    }
};