diff --git a/go/.exercism/config.json b/go/.exercism/config.json new file mode 100644 index 0000000..e0319fe --- /dev/null +++ b/go/.exercism/config.json @@ -0,0 +1,24 @@ +{ + "authors": [ + "lpil" + ], + "files": { + "solution": [ + "src/go.gleam" + ], + "test": [ + "test/go_test.gleam" + ], + "exemplar": [ + ".meta/example.gleam" + ], + "invalidator": [ + "gleam.toml", + "manifest.toml" + ] + }, + "forked_from": [ + "elm/go" + ], + "blurb": "Learn usage of Result by applying the rules of the game of Go" +} diff --git a/go/.exercism/metadata.json b/go/.exercism/metadata.json new file mode 100644 index 0000000..55767b3 --- /dev/null +++ b/go/.exercism/metadata.json @@ -0,0 +1 @@ +{"track":"gleam","exercise":"go","id":"5f92ded7f0d94054b5eb825d783c3245","url":"https://exercism.org/tracks/gleam/exercises/go","handle":"fw353qwgs","is_requester":true,"auto_approve":false} \ No newline at end of file diff --git a/go/.gitignore b/go/.gitignore new file mode 100644 index 0000000..170cca9 --- /dev/null +++ b/go/.gitignore @@ -0,0 +1,4 @@ +*.beam +*.ez +build +erl_crash.dump diff --git a/go/HELP.md b/go/HELP.md new file mode 100644 index 0000000..1ce5a0b --- /dev/null +++ b/go/HELP.md @@ -0,0 +1,32 @@ +# Help + +## Running the tests + +To run the tests, run the command `gleam test` from within the exercise directory. + +## Submitting your solution + +You can submit your solution using the `exercism submit src/go.gleam` command. +This command will upload your solution to the Exercism website and print the solution page's URL. + +It's possible to submit an incomplete solution which allows you to: + +- See how others have completed the exercise +- Request help from a mentor + +## Need to get help? + +If you'd like help solving the exercise, check the following pages: + +- The [Gleam track's documentation](https://exercism.org/docs/tracks/gleam) +- The [Gleam track's programming category on the forum](https://forum.exercism.org/c/programming/gleam) +- [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5) +- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs) + +Should those resources not suffice, you could submit your (incomplete) solution to request mentoring. + +To get help if you're having trouble, you can use one of the following resources: + +- [gleam.run](https://gleam.run/documentation/) is the gleam official documentation. +- [Discord](https://discord.gg/Fm8Pwmy) is the discord channel. +- [StackOverflow](https://stackoverflow.com/questions/tagged/gleam) can be used to search for your problem and see if it has been answered already. You can also ask and answer questions. \ No newline at end of file diff --git a/go/HINTS.md b/go/HINTS.md new file mode 100644 index 0000000..dc0f577 --- /dev/null +++ b/go/HINTS.md @@ -0,0 +1,5 @@ +# Hints + +- The [Result module][result-module] has some useful functions for working with the Result type, such as `result.map` and `result.try`. + +[result-module]: https://hexdocs.pm/gleam_stdlib/gleam/result.html \ No newline at end of file diff --git a/go/README.md b/go/README.md new file mode 100644 index 0000000..717665b --- /dev/null +++ b/go/README.md @@ -0,0 +1,107 @@ +# Go + +Welcome to Go on Exercism's Gleam Track. +If you need help running the tests or submitting your code, check out `HELP.md`. +If you get stuck on the exercise, check out `HINTS.md`, but try and solve it without using those first :) + +## Introduction + +## Results + +Gleam doesn't use exceptions for error handling, instead the generic `Result` type is returned by functions that can either succeed or fail. + +The `Result` type is built into the language so you don't need to define or import it, but if you were to define it yourself it would look like this: + +```gleam +pub type Result(value, error) { + Ok(value) + Error(error) +} +``` + +The `Ok` variant is returned when a function succeeds, and the `Error` variant is returned when a function fails. + +Results are very common in Gleam, and Gleam programmers will commonly use the [`gleam/result` module](https://hexdocs.pm/gleam_stdlib/gleam/result.html) to make working with them easier. + +The `result.map` function can be used to call a function on the value inside a result if it is an `Ok`, or to pass through an `Error` unchanged. + +```gleam +Ok(1) +|> result.map(fn(x) { x + 1 }) +// -> Ok(2) + +Error("Oh no!") +|> result.map(fn(x) { x + 1 }) +// -> Error("Oh no!") +``` + +The `result.try` function is similar, but the callback function is expected to return a result. This is useful for chaining together multiple functions that return results. + +```gleam +Ok(1) +|> result.try(fn(x) { Ok(x + 1) }) +// -> Ok(2) + +Ok(1) +|> result.try(fn(x) { Error("Nope!") }) +// -> Error("Nope!") +``` + +## Instructions + +In this exercise, you'll be applying the [rules of the game of Go](https://matmoore.github.io/learngo/). The rules themselves are already written, you just need to apply them in order to update the Game and to handle any errors / violations of the rules. + +The game is represented as follows: + +```gleam +pub type Player { + Black + White +} + + +pub type Game { + Game( + white_captured_stones: Int, + black_captured_stones: Int, + player: Player, + error: String, + ) +} +``` + +There are 4 rules in the game: + +- Each point can only have one stone. +- Opposition stones can be captured. +- You can not place a stone where it would capture itself. +- You can not use the same point twice. + +## 1. Apply the rules + +Write the content of the `apply_rules` function, which takes an initial `Game` and a set of rules, and returns the new `Game` after the rules have been applied. + +Three of the rules all check for violations of the rules and may return an error, and so have the return type of `Result(Game, String)`. If any of these rules fail, the original game should be returned, but with the `error` field updated with the relevant error. + +The other rule does not check for violations and so cannot fail (although it can return a changed `Game`), and so has the return type of `Game`. + +If all the rules pass, then any changes to `Game` from the rules should be kept, and the player should be changed. + +```gleam +pub fn apply_rules( + game: Game, + rule1: fn(Game) -> Result(Game, String), + rule2: fn(Game) -> Game, + rule3: fn(Game) -> Result(Game, String), + rule4: fn(Game) -> Result(Game, String), +) -> Game { + // -> If all rules pass, return a `Game` with all changes from the rules applied, and change player + // -> If any rule fails, return the original Game, but with the error field set +} +``` + +## Source + +### Created by + +- @lpil \ No newline at end of file diff --git a/go/gleam.toml b/go/gleam.toml new file mode 100644 index 0000000..29d7eb8 --- /dev/null +++ b/go/gleam.toml @@ -0,0 +1,14 @@ +name = "go" +version = "0.1.0" + +[dependencies] +gleam_otp = "~> 0.7 or ~> 1.0" +gleam_stdlib = ">= 0.54.0 or ~> 1.0" +simplifile = "~> 1.0" +gleam_erlang = ">= 0.25.0 and < 1.0.0" +gleam_yielder = ">= 1.1.0 and < 2.0.0" +gleam_regexp = ">= 1.1.0 and < 2.0.0" +gleam_deque = ">= 1.0.0 and < 2.0.0" + +[dev-dependencies] +exercism_test_runner = "~> 1.9" diff --git a/go/manifest.toml b/go/manifest.toml new file mode 100644 index 0000000..7b32e9a --- /dev/null +++ b/go/manifest.toml @@ -0,0 +1,31 @@ +# This file was generated by Gleam +# You typically do not need to edit this file + +packages = [ + { name = "argv", version = "1.0.2", build_tools = ["gleam"], requirements = [], otp_app = "argv", source = "hex", outer_checksum = "BA1FF0929525DEBA1CE67256E5ADF77A7CDDFE729E3E3F57A5BDCAA031DED09D" }, + { name = "exercism_test_runner", version = "1.9.0", build_tools = ["gleam"], requirements = ["argv", "gap", "glance", "gleam_community_ansi", "gleam_erlang", "gleam_json", "gleam_stdlib", "simplifile"], otp_app = "exercism_test_runner", source = "hex", outer_checksum = "0B17BB25F2FF1E60266467C24FE0CA04005410306AA05E9A4B41B1852D72865C" }, + { name = "filepath", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "67A6D15FB39EEB69DD31F8C145BB5A421790581BD6AA14B33D64D5A55DBD6587" }, + { name = "gap", version = "1.1.3", build_tools = ["gleam"], requirements = ["gleam_community_ansi", "gleam_stdlib"], otp_app = "gap", source = "hex", outer_checksum = "6EF5E3B523FDFBC317E9EA28D5163EE04744A97C007106F90207569789612291" }, + { name = "glance", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "glexer"], otp_app = "glance", source = "hex", outer_checksum = "E155BA1A787FD11827048355021C0390D2FE9A518485526F631A9D472858CC6D" }, + { name = "gleam_community_ansi", version = "1.4.3", build_tools = ["gleam"], requirements = ["gleam_community_colour", "gleam_regexp", "gleam_stdlib"], otp_app = "gleam_community_ansi", source = "hex", outer_checksum = "8A62AE9CC6EA65BEA630D95016D6C07E4F9973565FA3D0DE68DC4200D8E0DD27" }, + { name = "gleam_community_colour", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_stdlib"], otp_app = "gleam_community_colour", source = "hex", outer_checksum = "FDD6AC62C6EC8506C005949A4FCEF032038191D5EAAEC3C9A203CD53AE956ACA" }, + { name = "gleam_deque", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_deque", source = "hex", outer_checksum = "64D77068931338CF0D0CB5D37522C3E3CCA7CB7D6C5BACB41648B519CC0133C7" }, + { name = "gleam_erlang", version = "0.34.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "0C38F2A128BAA0CEF17C3000BD2097EB80634E239CE31A86400C4416A5D0FDCC" }, + { name = "gleam_json", version = "2.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "C55C5C2B318533A8072D221C5E06E5A75711C129E420DD1CE463342106012E5D" }, + { name = "gleam_otp", version = "0.16.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "50DA1539FC8E8FA09924EB36A67A2BBB0AD6B27BCDED5A7EF627057CF69D035E" }, + { name = "gleam_regexp", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_regexp", source = "hex", outer_checksum = "7F5E0C0BBEB3C58E57C9CB05FA9002F970C85AD4A63BA1E55CBCB35C15809179" }, + { name = "gleam_stdlib", version = "0.55.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "32D8F4AE03771516950047813A9E359249BD9FBA5C33463FDB7B953D6F8E896B" }, + { name = "gleam_yielder", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_yielder", source = "hex", outer_checksum = "8E4E4ECFA7982859F430C57F549200C7749823C106759F4A19A78AEA6687717A" }, + { name = "glexer", version = "2.2.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glexer", source = "hex", outer_checksum = "5C235CBDF4DA5203AD5EAB1D6D8B456ED8162C5424FE2309CFFB7EF438B7C269" }, + { name = "simplifile", version = "1.7.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "1D5DFA3A2F9319EC85825F6ED88B8E449F381B0D55A62F5E61424E748E7DDEB0" }, +] + +[requirements] +exercism_test_runner = { version = "~> 1.9" } +gleam_deque = { version = ">= 1.0.0 and < 2.0.0" } +gleam_erlang = { version = ">= 0.25.0 and < 1.0.0" } +gleam_otp = { version = "~> 0.7 or ~> 1.0" } +gleam_regexp = { version = ">= 1.1.0 and < 2.0.0" } +gleam_stdlib = { version = ">= 0.54.0 or ~> 1.0" } +gleam_yielder = { version = ">= 1.1.0 and < 2.0.0" } +simplifile = { version = "~> 1.0" } diff --git a/go/src/go.gleam b/go/src/go.gleam new file mode 100644 index 0000000..2f783d7 --- /dev/null +++ b/go/src/go.gleam @@ -0,0 +1,32 @@ +import gleam/result + +pub type Player { + Black + White +} + +pub type Game { + Game( + white_captured_stones: Int, + black_captured_stones: Int, + player: Player, + error: String, + ) +} + +pub fn apply_rules( + game: Game, + rule1: fn(Game) -> Result(Game, String), + rule2: fn(Game) -> Game, + rule3: fn(Game) -> Result(Game, String), + rule4: fn(Game) -> Result(Game, String), +) -> Game { + case rule2(game) |> rule1 |> result.try(rule3) |> result.try(rule4) { + Error(err) -> Game(..game, error: err) + Ok(g) -> + case g.player { + Black -> Game(..g, player: White) + White -> Game(..g, player: Black) + } + } +} diff --git a/go/test/go_test.gleam b/go/test/go_test.gleam new file mode 100644 index 0000000..61f3f0e --- /dev/null +++ b/go/test/go_test.gleam @@ -0,0 +1,110 @@ +import exercism/should +import exercism/test_runner +import go.{type Game, Black, Game, White} + +pub fn main() { + test_runner.main() +} + +fn identity(x: Game) -> Game { + x +} + +fn identity_rule(game: Game) -> Result(Game, String) { + Ok(game) +} + +fn error_rule(_game: Game, message: String) -> Result(Game, String) { + Error(message) +} + +fn new_game() -> Game { + Game(0, 0, White, "") +} + +fn ko_rule() -> String { + "Cannot repeat a previously played board position" +} + +fn liberty_rule() -> String { + "Cannot place a stone with no liberties" +} + +fn one_stone_per_point_rule() -> String { + "You can't put a stone on top of another stone" +} + +fn add_white_captured_stone(game: Game) -> Game { + Game(..game, white_captured_stones: 1) +} + +pub fn change_player_if_all_rules_pass_test() { + new_game() + |> go.apply_rules(identity_rule, identity, identity_rule, identity_rule) + |> should.equal(change_player(new_game())) +} + +pub fn retain_error_and_player_if_ko_rule_fails_test() { + new_game() + |> go.apply_rules(identity_rule, identity, identity_rule, error_rule( + _, + ko_rule(), + )) + |> should.equal(Game(..new_game(), error: ko_rule())) +} + +pub fn retain_error_and_player_if_liberty_rule_fails_test() { + new_game() + |> go.apply_rules( + identity_rule, + identity, + error_rule(_, liberty_rule()), + identity_rule, + ) + |> should.equal(Game(..new_game(), error: liberty_rule())) +} + +pub fn retain_error_and_player_if_one_stone_per_point_rule_fails_test() { + new_game() + |> go.apply_rules( + error_rule(_, one_stone_per_point_rule()), + identity, + identity_rule, + identity_rule, + ) + |> should.equal(Game(..new_game(), error: one_stone_per_point_rule())) +} + +pub fn retain_changes_from_capture_rule_and_change_player_test() { + new_game() + |> go.apply_rules( + identity_rule, + add_white_captured_stone, + identity_rule, + identity_rule, + ) + |> should.equal( + new_game() + |> add_white_captured_stone + |> change_player, + ) +} + +pub fn discard_changes_from_capture_rule_if_subsequent_rule_fails_test() { + new_game() + |> go.apply_rules( + identity_rule, + add_white_captured_stone, + identity_rule, + error_rule(_, ko_rule()), + ) + |> should.equal(Game(..new_game(), error: ko_rule())) +} + +fn change_player(game: Game) -> Game { + let new_player = case game.player { + White -> Black + Black -> White + } + Game(..game, player: new_player) +}